From 9c536ebe25b396d83596aef6e3a50cfb2a42a5ea Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Sun, 19 Apr 2020 19:42:22 +0200 Subject: [PATCH 01/24] First cut at a readme. --- tools/digital-signing.md | 162 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 tools/digital-signing.md diff --git a/tools/digital-signing.md b/tools/digital-signing.md new file mode 100644 index 00000000000..6b8dc4d2c31 --- /dev/null +++ b/tools/digital-signing.md @@ -0,0 +1,162 @@ +Digital Signing of Over The Air (OTA) updates. + +The espota.py tools contains support for signed, over the air, +firmware updates. This works by + +1) Including a public (i.e. not secret, not sensitve) + key in your ESP firmware. + +2) Signing your binary with the private key (which you + keep secure). + +3) Send the signature and the firmware to the ESP32 + using the normal OTA protocol. + +4) Have the ESP32 check the signature validity against + its build in public key(s). + + Verify if the signature signed the digest of the + firmware. + + Verify if the firmware uploaded had that digest. + + (Optional) Verify that the signature is dated + later than the current firmware compile time. This + is to foil a 'downgrade' attack; where one tries + to go back to an older version of the firmware + (that was once signed and) that is attractive as + it has a security hole in it. + +5) And if that is the case; activate the partition + and reboot. + +The technology used for this is a signed digital timestamp +according to RFC 3161. The signature make use of a PKI +X.509 hierarchy. This allows for long lived keys, federation +and (administrattive) separation of concerns. In particular +it allows you to manage signatures over long periods +of time, even staff comes and leaves, keys need to be +recycled or are lost. + + +A) I just want to play. How do I do that ? + +Take the example 'SecureOTA'. + +This example has the root public key build of the public, +redwax.eu interop server (that signs anything you offer it): + + https://interop.redwax.eu/rs/timestamp/ + +Compile the SecureOTA-RedWax and transfer it to the +Arduino by Serial or by normal OTA. Enabling debug or verbose +logging will give you a blow by blow account of the process. + +Once installed - you can only OTA update it with firmware +that is signed by above demo server. Now as anyone can +get anything signed - this is not particularly secure -- but +this is a good proxy for a well set up CI/CD system; where the +role of that interop.redwax.eu server would be a service +in your own environment (we'll get to why that is a good idea +later). So to install you run: + + ./espota.py -i -f .../SecureOTA.bin \ + --sign=https://interop.redwax.eu/test/timestamp + +while you watch the serial output. + +B) OK - so how do I this secure, without any server noncense ? + +So above example is just that; with no real security. Again +takethe example 'SecureOTA' and open it. + +The next step would be to generate a public/private key; by doing + + ./espota.py --signing-key=secret-signing-key.pem --generate-signing-key + +this will write out the secret file to disk; but also show you +the public (i.e. the non secret key) in an easy to include in +C format. Cut the bit from 'const unsigned char ... { .. }' and +paste it into the SecureOTA.ino example. + +Now change or add a line that says: + + signatureChecker.addTrustedCertAsDER(signing_cert, sizeof(signing_cert)); + +If you leave in the existing line - then the Uploader will accept both your +key and RedWax signed (insecure ones). Now tranfer this by serial or +over OTA to the ESP32 (if you still have the version of example A in the +ESP32 - then you can use the above ./espota.py .. --sign=http.. - as the +current firmware will still accept RedWax signed uploads. + +Once it is in - you can use your own key (in the file + + secret-signing-key.pem + +) to sign and upload: + + ./espota.py -i -f .../SecureOTA.bin \ + --signing-key=secret-signing-key.pem + +C) So what happens if I loose that private key. + +Well then, if the firmware which is in the ESP32 only has the signing_cert +sent to signatureChecker.addTrustedCertAsDER, then it will only accept +OTA uploads with that key. And as you cannot derive the private key from +the public one (in the code, in the firmware, extratable from the ESP32), +you essentially are blocked from any OTA. It will need a serial update +to a known key pair first. + +The first line defense agains this is usually to put 2 or 3 of +such keys into the Fireware (simply run the '-G/--generate command +sever times), print out some of the keys, and hope you do not loose +them or that someone else gets access to it. + +And the latter is always a bit of an issue; as you need this key +everytime you do a test, a compile, etc. + +D) That sounds a bit messy in an enterprise / serious setting. + +That is indeed the case. So this is what PKI has been invented for. What +you do here is that you use a hierarchy of signatures. So suppose +you have a hierarchy of: + + myCorp Certifcate Authority + | + +---- production + | | + | +--- product 1 + | + +---- engineering + | + +---- development + | | + | +- Fred + | +- Mary + | + +---- pilot-testing + +Where myCorp has signed production and engineering. And production has +signed plant . With engineering having signed development and pilot +testing. And within development Fred and Mary have their own certificate. + +Now a piece of firmware that has been given the myCorp certificate can +validate any of its leafs. I.e. it can accept Freds or Marys their +work from their desktop; but also be updated for production. While it is +also possible to limit the scope of some firmware to just accepting +firmware that has been signed by production (or at least engineering, +pilot-testing). + +Now while it may be fine for Fred and Mary to have those signing keys +on their personal machines; it is generally not desirable to have such +keys on the relatively exposed CI/CD systems. And for keys that carry +a lot of power - you often want to keep those very contained and secure. + +This is where the RFC3161 time servers enter into the equation (the URL +used in example A). Such servers are easy to set up (about as hard as +a webserver) and very easy to secure. It then becomes possible to +maintain most top level keys 'off-line' (on paper, on a chipcard or +in an HSM are common choises), have important keys, such as those +for production, well contained in a time-server. Yet easily accessible +for those who need regular access. + From 048b4763798f4f832aca386d8b966eb7aa27b676 Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Sun, 19 Apr 2020 21:08:00 +0200 Subject: [PATCH 02/24] Refactored the Updater to allow for a filter/processor (both for OTA and SD/web uploads) Refactored the ArduinoOTA code to allow multiple auth mechisms, including 'better than MD5' digest auth. Added a RFC 3161 signed OTA security layer (with X.509 certs). Currently requires some glue code that long term will/should move into mbedtls. Updated espota.py to support: - digests other than MD5 (e.g. SHA256) - local public/private key pair signed OTA - RFC 3161 Timeserver/signature based signing (so that the key does not need to be on a build server. See also redwax.ey. --- .../examples/SecureOTA/SecureOTA.ino | 79 ++ .../examples/SecureOTA/hardcoded-roots.cpp | 98 +++ .../examples/SecureOTA/hardcoded-roots.h | 5 + libraries/ArduinoOTA/src/ArduinoOTA.cpp | 830 ++++++++++++------ libraries/ArduinoOTA/src/ArduinoOTA.h | 62 +- libraries/Update/src/Update.h | 156 ++-- libraries/Update/src/UpdateProcessor.cpp | 38 + libraries/Update/src/UpdateProcessor.h | 51 ++ .../Update/src/UpdateProcessorRFC3161.cpp | 383 ++++++++ libraries/Update/src/UpdateProcessorRFC3161.h | 37 + libraries/Update/src/Updater.cpp | 708 ++++++++------- libraries/Update/src/mbedtls-ts-addons/sign.h | 85 ++ .../src/mbedtls-ts-addons/signer_info.cpp | 231 +++++ libraries/Update/src/mbedtls-ts-addons/ts.cpp | 739 ++++++++++++++++ libraries/Update/src/mbedtls-ts-addons/ts.h | 247 ++++++ .../src/mbedtls-ts-addons/x509_ts_utils.cpp | 371 ++++++++ .../src/mbedtls-ts-addons/x509_ts_utils.h | 47 + tools/espota.py | 277 +++++- 18 files changed, 3732 insertions(+), 712 deletions(-) create mode 100644 libraries/ArduinoOTA/examples/SecureOTA/SecureOTA.ino create mode 100644 libraries/ArduinoOTA/examples/SecureOTA/hardcoded-roots.cpp create mode 100644 libraries/ArduinoOTA/examples/SecureOTA/hardcoded-roots.h create mode 100644 libraries/Update/src/UpdateProcessor.cpp create mode 100644 libraries/Update/src/UpdateProcessor.h create mode 100644 libraries/Update/src/UpdateProcessorRFC3161.cpp create mode 100644 libraries/Update/src/UpdateProcessorRFC3161.h create mode 100644 libraries/Update/src/mbedtls-ts-addons/sign.h create mode 100644 libraries/Update/src/mbedtls-ts-addons/signer_info.cpp create mode 100644 libraries/Update/src/mbedtls-ts-addons/ts.cpp create mode 100644 libraries/Update/src/mbedtls-ts-addons/ts.h create mode 100644 libraries/Update/src/mbedtls-ts-addons/x509_ts_utils.cpp create mode 100644 libraries/Update/src/mbedtls-ts-addons/x509_ts_utils.h diff --git a/libraries/ArduinoOTA/examples/SecureOTA/SecureOTA.ino b/libraries/ArduinoOTA/examples/SecureOTA/SecureOTA.ino new file mode 100644 index 00000000000..984c7428721 --- /dev/null +++ b/libraries/ArduinoOTA/examples/SecureOTA/SecureOTA.ino @@ -0,0 +1,79 @@ +#include +#include +#include +#include "ArduinoOTA.h" +#include "UpdateProcessor.h" +#include "UpdateProcessorRFC3161.h" + +#include "hardcoded-roots.h" + +UpdateProcessorRFC3161 rfcChecker = UpdateProcessorRFC3161(); + +void setup() { + Serial.begin(115200); + Serial.println("Booting " __DATE__ " " __TIME__); + + WiFi.mode(WIFI_STA); + WiFi.begin("wifi-network", "password"); + while (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.println("Connection Failed! Rebooting..."); + delay(5000); + ESP.restart(); + } + + // ArduinoOTA.setPort(3232); + // ArduinoOTA.setHostname("some-name"); + + // No authentication by default + // ArduinoOTA.setPassword(OTA_PASSWD); + + // Specify a (root) signature we trust during + // updates. + rfcChecker.addTrustedCertAsDER(ca_interop_redwax_der, ca_interop_redwax_der_len); + + // Allow unsiged uploads: + // rfcChecker.setAllowLegacyUploads(true); // default is not to allow this. + + // Wire this check into the normal upload process. + // + ArduinoOTA.setProcessor(&rfcChecker); + + ArduinoOTA + .onStart([]() { + String type; + if (ArduinoOTA.getCommand() == U_FLASH) + type = "sketch"; + else // U_SPIFFS + type = "filesystem"; + + // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() + Serial.println("Updating " + type); + }) + .onEnd([]() { + Serial.println(" Ok, completed without errors."); + }) + .onError([](ota_error_t error) { + Serial.printf("\nAborted with error[%u]: ", error); + if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); + else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); + else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); + else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); + else if (error == OTA_END_ERROR) Serial.println("End Failed"); + }); + + ArduinoOTA.approveReboot([]() { + Serial.println("Reboot ok"); + return true; + }); + + ArduinoOTA.begin(); + + Serial.println("Ready"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); +} + +void loop() { + ArduinoOTA.handle(); +} + diff --git a/libraries/ArduinoOTA/examples/SecureOTA/hardcoded-roots.cpp b/libraries/ArduinoOTA/examples/SecureOTA/hardcoded-roots.cpp new file mode 100644 index 00000000000..3b34b0fc209 --- /dev/null +++ b/libraries/ArduinoOTA/examples/SecureOTA/hardcoded-roots.cpp @@ -0,0 +1,98 @@ +#include "hardcoded-roots.h" + +/* Root/Certificate Authority (CA) used by the free/demo + * 'sign anything' server at: https://interop.redwax.eu/rs/timestamp/ + * + * Encoded as 'DER' - can easily be (re)created with the 'xxd -i' + * tool on unix. + */ +const unsigned int ca_interop_redwax_der_len = 1041; +const unsigned char ca_interop_redwax_der[] = { + 0x30, 0x82, 0x04, 0x0d, 0x30, 0x82, 0x02, 0xf5, 0xa0, 0x03, 0x02, 0x01, + 0x02, 0x02, 0x14, 0x6f, 0x11, 0xb7, 0xd8, 0x55, 0xd2, 0x7d, 0x9a, 0x14, + 0xf3, 0xb6, 0xe9, 0x15, 0x2b, 0x60, 0xca, 0x8c, 0x4b, 0xe2, 0xaa, 0x30, + 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, + 0x05, 0x00, 0x30, 0x5a, 0x31, 0x3f, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x04, + 0x03, 0x13, 0x36, 0x52, 0x65, 0x64, 0x77, 0x61, 0x78, 0x20, 0x49, 0x6e, + 0x74, 0x65, 0x72, 0x6f, 0x70, 0x20, 0x54, 0x65, 0x73, 0x74, 0x69, 0x6e, + 0x67, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x74, 0x79, 0x20, 0x32, 0x30, 0x34, 0x30, 0x31, 0x17, 0x30, + 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x52, 0x65, 0x64, 0x77, + 0x61, 0x78, 0x20, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x30, 0x1e, + 0x17, 0x0d, 0x32, 0x30, 0x30, 0x32, 0x31, 0x31, 0x31, 0x36, 0x33, 0x38, + 0x35, 0x36, 0x5a, 0x17, 0x0d, 0x34, 0x30, 0x30, 0x32, 0x30, 0x36, 0x31, + 0x36, 0x33, 0x38, 0x35, 0x36, 0x5a, 0x30, 0x5a, 0x31, 0x3f, 0x30, 0x3d, + 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x36, 0x52, 0x65, 0x64, 0x77, 0x61, + 0x78, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6f, 0x70, 0x20, 0x54, 0x65, + 0x73, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x32, 0x30, 0x34, + 0x30, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, + 0x52, 0x65, 0x64, 0x77, 0x61, 0x78, 0x20, 0x50, 0x72, 0x6f, 0x6a, 0x65, + 0x63, 0x74, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, + 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xe7, + 0x20, 0x27, 0x23, 0x18, 0x5f, 0x44, 0x70, 0x7d, 0x24, 0x46, 0xef, 0x53, + 0x82, 0xa8, 0x97, 0x01, 0x5f, 0x98, 0x74, 0x15, 0x0e, 0x8d, 0x5e, 0x23, + 0x1b, 0xdc, 0x02, 0x6c, 0x97, 0x59, 0x32, 0xfe, 0xad, 0x19, 0x85, 0x98, + 0x20, 0xfb, 0x33, 0xac, 0xad, 0xc2, 0x9b, 0x0e, 0x3d, 0xb8, 0xc6, 0xcc, + 0x80, 0x01, 0x0b, 0xfe, 0x10, 0x76, 0x9a, 0xbf, 0xe0, 0x64, 0x81, 0x4c, + 0x43, 0x4a, 0xec, 0xd8, 0x65, 0xb0, 0x91, 0xa2, 0xa8, 0x96, 0xf8, 0x34, + 0xaf, 0x0f, 0x60, 0x13, 0xc3, 0xf0, 0x47, 0x6a, 0x7a, 0x8e, 0xd8, 0x6e, + 0x2f, 0xe0, 0xb3, 0xa1, 0xcc, 0x87, 0x91, 0xcd, 0xbb, 0x7f, 0xf5, 0x06, + 0x85, 0xe9, 0x18, 0x4a, 0x76, 0xdc, 0x80, 0x88, 0xad, 0x14, 0xdb, 0x1e, + 0x68, 0x95, 0x84, 0x23, 0xf7, 0x7e, 0x78, 0x5f, 0x0b, 0x3e, 0x43, 0xd7, + 0x86, 0xc4, 0x94, 0x56, 0x40, 0x85, 0xff, 0x07, 0x04, 0x1b, 0x6c, 0x4c, + 0x0e, 0x04, 0xb5, 0x29, 0xc9, 0x28, 0x17, 0xb1, 0x09, 0xf5, 0x3b, 0x1c, + 0xed, 0x74, 0x55, 0x43, 0x70, 0xe8, 0xab, 0x95, 0x8b, 0xf8, 0xd0, 0x82, + 0x9e, 0xe4, 0xa6, 0x78, 0xc7, 0x15, 0x4c, 0x68, 0x48, 0xf5, 0x76, 0x86, + 0xad, 0xeb, 0xbe, 0x0d, 0x86, 0x26, 0x09, 0x1d, 0xef, 0x9c, 0x35, 0xda, + 0xbf, 0x1e, 0xbb, 0x03, 0x8e, 0x6f, 0x95, 0x87, 0x20, 0x25, 0x4e, 0x29, + 0x0c, 0xbf, 0xe8, 0x33, 0x02, 0x56, 0xf9, 0x62, 0xa2, 0xc0, 0xa8, 0xe9, + 0x89, 0x99, 0xb8, 0x93, 0x24, 0x59, 0x59, 0x81, 0xc6, 0x2e, 0x50, 0x94, + 0x6f, 0xe7, 0x24, 0x79, 0xd8, 0x4c, 0xd5, 0xe0, 0x99, 0xc3, 0x0b, 0x20, + 0xc8, 0x92, 0xa6, 0xd1, 0xa3, 0xc9, 0x6d, 0x30, 0xe3, 0x96, 0xa4, 0x41, + 0xb2, 0x15, 0x3e, 0xfb, 0x86, 0x99, 0x31, 0x5d, 0x80, 0x15, 0xd2, 0xd1, + 0x21, 0x5a, 0xbb, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xca, 0x30, + 0x81, 0xc7, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, + 0x14, 0xed, 0x75, 0xde, 0x35, 0x14, 0x3c, 0x47, 0x23, 0xf1, 0xb1, 0x1a, + 0xe4, 0x13, 0x43, 0x8c, 0xbb, 0xcc, 0xc2, 0x2b, 0x56, 0x30, 0x81, 0x97, + 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x81, 0x8f, 0x30, 0x81, 0x8c, 0x80, + 0x14, 0xed, 0x75, 0xde, 0x35, 0x14, 0x3c, 0x47, 0x23, 0xf1, 0xb1, 0x1a, + 0xe4, 0x13, 0x43, 0x8c, 0xbb, 0xcc, 0xc2, 0x2b, 0x56, 0xa1, 0x5e, 0xa4, + 0x5c, 0x30, 0x5a, 0x31, 0x3f, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x04, 0x03, + 0x13, 0x36, 0x52, 0x65, 0x64, 0x77, 0x61, 0x78, 0x20, 0x49, 0x6e, 0x74, + 0x65, 0x72, 0x6f, 0x70, 0x20, 0x54, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, + 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x74, 0x79, 0x20, 0x32, 0x30, 0x34, 0x30, 0x31, 0x17, 0x30, 0x15, + 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x52, 0x65, 0x64, 0x77, 0x61, + 0x78, 0x20, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x82, 0x14, 0x6f, + 0x11, 0xb7, 0xd8, 0x55, 0xd2, 0x7d, 0x9a, 0x14, 0xf3, 0xb6, 0xe9, 0x15, + 0x2b, 0x60, 0xca, 0x8c, 0x4b, 0xe2, 0xaa, 0x30, 0x0c, 0x06, 0x03, 0x55, + 0x1d, 0x13, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0d, 0x06, + 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, + 0x03, 0x82, 0x01, 0x01, 0x00, 0xce, 0x34, 0xe6, 0x5c, 0x68, 0x40, 0x77, + 0x1e, 0x32, 0xe2, 0xe3, 0x00, 0xa4, 0x79, 0x2b, 0x9c, 0x1a, 0x09, 0x83, + 0xc8, 0x2d, 0x9b, 0xc0, 0xf2, 0x2f, 0x66, 0x88, 0xeb, 0xf5, 0x43, 0xb1, + 0xe3, 0x91, 0x72, 0x07, 0x96, 0xb0, 0x85, 0x18, 0x14, 0x3f, 0xfa, 0x4a, + 0xc1, 0x92, 0x03, 0x27, 0xbc, 0xb9, 0x6f, 0x51, 0x9d, 0xdb, 0x40, 0x0a, + 0x8e, 0x46, 0x9c, 0xde, 0x48, 0x97, 0x11, 0x34, 0xe3, 0x0a, 0xf1, 0x92, + 0x9b, 0x7b, 0xfd, 0x10, 0xab, 0x4a, 0x72, 0x5c, 0xa4, 0x77, 0x72, 0x5d, + 0xa0, 0xa2, 0xb5, 0x5a, 0xea, 0x4a, 0x85, 0x0c, 0x68, 0x4d, 0xbe, 0xaa, + 0x4d, 0xa9, 0x30, 0x6a, 0x1c, 0x5f, 0xf6, 0x78, 0x20, 0xb2, 0x3b, 0x74, + 0xbb, 0xe1, 0xbd, 0x9e, 0x09, 0xc7, 0x26, 0x22, 0x11, 0x75, 0xe8, 0xcf, + 0xf1, 0x32, 0x00, 0xb7, 0x16, 0x55, 0x28, 0x09, 0xf5, 0x46, 0x2c, 0x3b, + 0x39, 0xd3, 0x56, 0xce, 0xb3, 0x5d, 0xab, 0xd1, 0x3f, 0xe1, 0x8b, 0x2b, + 0xf6, 0x6e, 0x63, 0x11, 0x80, 0x65, 0x64, 0xa1, 0xcb, 0x02, 0x4c, 0xaf, + 0x96, 0xd8, 0xd0, 0x7d, 0xeb, 0x7d, 0x7e, 0xc3, 0x02, 0x1b, 0xb4, 0xf2, + 0xf6, 0x4c, 0x58, 0x55, 0x7e, 0xc0, 0xb3, 0x34, 0xc4, 0x06, 0x47, 0x87, + 0xe1, 0x97, 0x79, 0x82, 0xe6, 0xd3, 0x5f, 0x6a, 0x61, 0x8c, 0xc6, 0xd0, + 0x93, 0xeb, 0xb3, 0x1e, 0xda, 0x00, 0x40, 0x5e, 0x04, 0xe6, 0x6c, 0x2b, + 0x84, 0xd8, 0xee, 0x32, 0x7e, 0xaa, 0xd8, 0x23, 0x81, 0xf8, 0xcd, 0xb9, + 0xe2, 0xef, 0xe9, 0x19, 0x0b, 0x22, 0xde, 0x2d, 0x4b, 0x33, 0x10, 0x43, + 0x72, 0x1e, 0x40, 0x2a, 0xfc, 0x63, 0x3e, 0xc3, 0xb7, 0xa4, 0xb1, 0x62, + 0xd6, 0x85, 0xaf, 0x90, 0x93, 0x40, 0xac, 0xac, 0x37, 0x0c, 0x92, 0xdc, + 0x4e, 0x70, 0x90, 0x97, 0xb5, 0xa7, 0xe1, 0x9c, 0xd1 +}; diff --git a/libraries/ArduinoOTA/examples/SecureOTA/hardcoded-roots.h b/libraries/ArduinoOTA/examples/SecureOTA/hardcoded-roots.h new file mode 100644 index 00000000000..8788bce03b9 --- /dev/null +++ b/libraries/ArduinoOTA/examples/SecureOTA/hardcoded-roots.h @@ -0,0 +1,5 @@ +#ifndef _H_HARDCODED_CA +#define _H_HARDCODED_CA +extern const unsigned int ca_interop_redwax_der_len; +extern const unsigned char ca_interop_redwax_der[]; +#endif diff --git a/libraries/ArduinoOTA/src/ArduinoOTA.cpp b/libraries/ArduinoOTA/src/ArduinoOTA.cpp index 884c9d341c1..ca890543460 100644 --- a/libraries/ArduinoOTA/src/ArduinoOTA.cpp +++ b/libraries/ArduinoOTA/src/ArduinoOTA.cpp @@ -1,381 +1,629 @@ + #ifndef LWIP_OPEN_SRC #define LWIP_OPEN_SRC #endif + #include #include #include "ArduinoOTA.h" #include "ESPmDNS.h" #include "MD5Builder.h" #include "Update.h" - +#include // #define OTA_DEBUG Serial ArduinoOTAClass::ArduinoOTAClass() -: _port(0) -, _initialized(false) -, _rebootOnSuccess(true) -, _mdnsEnabled(true) -, _state(OTA_IDLE) -, _size(0) -, _cmd(0) -, _ota_port(0) -, _ota_timeout(1000) -, _start_callback(NULL) -, _end_callback(NULL) -, _error_callback(NULL) -, _progress_callback(NULL) + : _port(0) + , _initialized(false) + , _rebootOnSuccess(true) + , _mdnsEnabled(true) + , _state(OTA_IDLE) + , _size(0) + , _cmd(0) + , _ota_port(0) + , _ota_timeout(1000) + , _md_info(NULL) + , _md_auth_info(NULL) + , _md_ctx(NULL) + , _md_min(MBEDTLS_MD_MD5) + , _digestAsHexString("") + , _start_callback(NULL) + , _end_callback(NULL) + , _error_callback(NULL) + , _progress_callback(NULL) + , _approve_reboot_callback(NULL) { } -ArduinoOTAClass::~ArduinoOTAClass(){ - _udp_ota.stop(); +ArduinoOTAClass::~ArduinoOTAClass() { + _udp_ota.stop(); + if (_md_ctx) { + mbedtls_md_free(_md_ctx); + free(_md_ctx); + }; + _md_ctx = NULL; } ArduinoOTAClass& ArduinoOTAClass::onStart(THandlerFunction fn) { - _start_callback = fn; - return *this; + _start_callback = fn; + return *this; } ArduinoOTAClass& ArduinoOTAClass::onEnd(THandlerFunction fn) { - _end_callback = fn; - return *this; + _end_callback = fn; + return *this; } ArduinoOTAClass& ArduinoOTAClass::onProgress(THandlerFunction_Progress fn) { - _progress_callback = fn; - return *this; + _progress_callback = fn; + return *this; } ArduinoOTAClass& ArduinoOTAClass::onError(THandlerFunction_Error fn) { - _error_callback = fn; - return *this; + _error_callback = fn; + return *this; +} + +ArduinoOTAClass& ArduinoOTAClass::approveReboot(THandlerFunction_ApproveReboot fn) { + _approve_reboot_callback = fn; + return *this; } + ArduinoOTAClass& ArduinoOTAClass::setPort(uint16_t port) { - if (!_initialized && !_port && port) { - _port = port; - } - return *this; + if (!_initialized && !_port && port) { + _port = port; + } + return *this; } ArduinoOTAClass& ArduinoOTAClass::setHostname(const char * hostname) { - if (!_initialized && !_hostname.length() && hostname) { - _hostname = hostname; - } - return *this; + if (!_initialized && !_hostname.length() && hostname) { + _hostname = hostname; + } + return *this; } String ArduinoOTAClass::getHostname() { - return _hostname; + return _hostname; } -ArduinoOTAClass& ArduinoOTAClass::setPassword(const char * password) { - if (!_initialized && !_password.length() && password) { - MD5Builder passmd5; - passmd5.begin(); - passmd5.add(password); - passmd5.calculate(); - _password = passmd5.toString(); - } - return *this; +ArduinoOTAClass& ArduinoOTAClass::setPassword(const char *password, const char * digest_type) { + String p = String(password); + if (digest_type == NULL) + digest_type = "MD5"; + + if (!_initialized && !_hostname.length() && password) + _password = _hexDigest(mbedtls_md_info_from_string(digest_type), p); + + return *this; } ArduinoOTAClass& ArduinoOTAClass::setPasswordHash(const char * password) { - if (!_initialized && !_password.length() && password) { - _password = password; - } - return *this; + if (!_initialized && !_password.length() && password) { + _password = password; + } + return *this; +} + +ArduinoOTAClass& ArduinoOTAClass::setRebootOnSuccess(bool reboot) { + _rebootOnSuccess = reboot; + return *this; } -ArduinoOTAClass& ArduinoOTAClass::setRebootOnSuccess(bool reboot){ - _rebootOnSuccess = reboot; - return *this; +ArduinoOTAClass& ArduinoOTAClass::setMdnsEnabled(bool enabled) { + _mdnsEnabled = enabled; + return *this; } -ArduinoOTAClass& ArduinoOTAClass::setMdnsEnabled(bool enabled){ - _mdnsEnabled = enabled; - return *this; +ArduinoOTAClass& ArduinoOTAClass::setProcessor(UpdateProcessor * processor) { + Update.setProcessor(processor); + return *this; } void ArduinoOTAClass::begin() { - if (_initialized){ - log_w("already initialized"); - return; - } + if (_initialized) { + log_w("already initialized"); + return; + } + + if (!_port) { + _port = 3232; + } + + if (!_udp_ota.begin(_port)) { + log_e("udp bind failed"); + return; + } + + if (!_hostname.length()) { + char tmp[20]; + uint8_t mac[6]; + WiFi.macAddress(mac); + sprintf(tmp, "esp32-%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + _hostname = tmp; + } + if (_mdnsEnabled) { + MDNS.begin(_hostname.c_str()); + MDNS.enableArduino(_port, (_password.length() > 0)); + } + _initialized = true; + _state = OTA_IDLE; + log_i("OTA server at: %s.local:%u", _hostname.c_str(), _port); +} - if (!_port) { - _port = 3232; +int ArduinoOTAClass::parseInt() { + char data[INT_BUFFER_SIZE]; + uint8_t index = 0; + char value; + while (_udp_ota.peek() == ' ') _udp_ota.read(); + while (index < INT_BUFFER_SIZE - 1) { + value = _udp_ota.peek(); + if (value < '0' || value > '9') { + data[index++] = '\0'; + return atoi(data); } + data[index++] = _udp_ota.read(); + } + return 0; +} - if(!_udp_ota.begin(_port)){ - log_e("udp bind failed"); - return; +String ArduinoOTAClass::readStringUntil(char end) { + String res = ""; + int value; + while (true) { + value = _udp_ota.read(); + if (value <= 0 || value == end) { + return res; } + res += (char)value; + } + return res; +} +// default / original format +char * ArduinoOTAClass::_processsOldStyleHeader() { + _cmd = parseInt(); + _ota_port = parseInt(); + _size = parseInt(); + _udp_ota.read(); + + _digestAsHexString = readStringUntil('\n'); + _digestAsHexString.trim(); + + if (_digestAsHexString.length() == 32) + _md_info = mbedtls_md_info_from_type(MBEDTLS_MD_MD5); + else if (_digestAsHexString.length() == 40) + _md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); + else if (_digestAsHexString.length() == 64) + _md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + else { + return (char *)"Unknown digest length"; + }; + + // The password, if any; is always MD5; + _md_auth_info = mbedtls_md_info_from_type(MBEDTLS_MD_MD5); + return NULL; +} + +char * ArduinoOTAClass::_processsHeader() { + char buff[256]; + + _ota_port = _size = 0; + _md_info = NULL; + _cmd = -1; + _digestAsHexString = ""; + + if (isdigit(_udp_ota.peek())) + return _processsOldStyleHeader(); + + if ((_udp_ota.readBytesUntil('/', buff, sizeof(buff)) != 6) || bcmp(buff, "RedWax", 6)) + return (char *)"Unknown protocol, ignoring."; + + if (_udp_ota.readStringUntil(' ').toInt() != 1) + return (char *)"Can only handle the RedWax/1.XX hdr"; + + bool eol = false; + while (!eol) { + size_t l; + + while (_udp_ota.peek() == ' ') + _udp_ota.read(); + + if (_udp_ota.peek() == '\n') + break; - if (!_hostname.length()) { - char tmp[20]; - uint8_t mac[6]; - WiFi.macAddress(mac); - sprintf(tmp, "esp32-%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - _hostname = tmp; + // readStringUntil a SPACE or a NL; and bail on ay non-ASCCI + // chars in the header. + // + for (l = 0; l < sizeof(buff) - 1; l++) { + buff[l] = _udp_ota.read(); + if (buff[l] > ' ' && buff[l] < 127 && l < sizeof(buff) - 1) + continue; + if (buff[l] != '\n' && buff[l] != ' ') + return (char *)"Illegal char/length of RedWax/1.XX hdr"; + break; } - if(_mdnsEnabled){ - MDNS.begin(_hostname.c_str()); - MDNS.enableArduino(_port, (_password.length() > 0)); + eol = (buff[l] == '\n'); + buff[l] = 0; + + if (0 == bcmp("cmd=", buff, 4)) + _cmd = atoi(buff + 4); + else if (0 == bcmp("port=", buff, 5)) + _ota_port = atoi(buff + 5); + else if (0 == bcmp("size=", buff, 5)) + _size = atoi(buff + 5); + else if (0 == bcmp("digest=", buff, 7)) + _digestAsHexString = String(buff + 7); + else if (0 == bcmp("md=", buff, 3)) { + if ((_md_info = mbedtls_md_info_from_string(buff + 3)) == NULL) + return (char *)"Unknown digest"; } - _initialized = true; - _state = OTA_IDLE; - log_i("OTA server at: %s.local:%u", _hostname.c_str(), _port); + else if (0 == bcmp("authmd=", buff, 7) && ((_md_auth_info = mbedtls_md_info_from_string(buff + 7)) == NULL)) { + return (char *)"Unknown auth digest"; + } else { + log_i("Ignoring OTA key/value param '%s'", buff); + }; + } // while loop. + + if (!_ota_port || !_size || !_md_info || _cmd == -1 || _digestAsHexString == "" ) + return (char *)"Midding OTA parameters"; + + if (_digestAsHexString.length() != 2 * mbedtls_md_get_size(_md_info)) + return (char *)"Wrong digest size"; + + return NULL; } -int ArduinoOTAClass::parseInt(){ - char data[INT_BUFFER_SIZE]; - uint8_t index = 0; - char value; - while(_udp_ota.peek() == ' ') _udp_ota.read(); - while(index < INT_BUFFER_SIZE - 1){ - value = _udp_ota.peek(); - if(value < '0' || value > '9'){ - data[index++] = '\0'; - return atoi(data); - } - data[index++] = _udp_ota.read(); - } - return 0; + +void ArduinoOTAClass::_onRxHeaderPhase() { + char * e; + if ((e = _processsHeader()) != NULL) { + _reportErrorAndAbort(OTA_BEGIN_ERROR, (char *)"Header error: %s", e); + return; + }; + + if (_cmd != U_FLASH && _cmd != U_SPIFFS) { + _reportErrorAndAbort(OTA_BEGIN_ERROR, (char *)"Unknown command (%04x)", _cmd); + return; + }; + + log_i("OTA Outer Digest: %s: %s", mbedtls_md_get_name(_md_info), _digestAsHexString.c_str()); + + if (mbedtls_md_get_type(_md_info) < _md_min) { + _reportErrorAndAbort(OTA_AUTH_ERROR, (char *)"Insecure digest (%s or better expected)", mbedtls_md_get_name(mbedtls_md_info_from_type(_md_min))); + return; + }; + + for (int i = 0; i < mbedtls_md_get_size(_md_info); i++) { + _md_buff[i] = strtoul(_digestAsHexString.substring(i * 2, i * 2 + 2).c_str(), NULL, 16); + }; + + int ret; + if (_md_ctx) { + mbedtls_md_free(_md_ctx); + free(_md_ctx); + } + if ((_md_ctx = (mbedtls_md_context_t *)malloc(sizeof(*_md_ctx))) == NULL) + { + _reportErrorAndAbort(OTA_AUTH_ERROR, (char *)"could not malloc digest"); + return; + } + mbedtls_md_init(_md_ctx); + if ((ret = mbedtls_md_setup(_md_ctx, _md_info, 0)) != 0) { + _reportErrorAndAbort(OTA_AUTH_ERROR, (char *)"could not setup digest: %x", -ret); + return; + } + // beyond here we rely on the _state going back to IDE for + // cleaning up the digest memory. + + if (_password.length()) { + if (mbedtls_md_get_type(_md_auth_info) < _md_min) { + _reportErrorAndAbort(OTA_AUTH_ERROR, (char *)"Insecure Auth Digest (%s or better expected)", mbedtls_md_get_name(mbedtls_md_info_from_type(_md_min))); + return; + }; + + log_i("Auth Digest: %s\n", mbedtls_md_get_name(_md_auth_info)); + + String seed = String(micros()) + String(esp_random()) + _hostname; + + _nonce = _hexDigest(_md_auth_info, seed); + if (_nonce.length() != mbedtls_md_get_size(_md_auth_info) * 2) { + _reportErrorAndAbort(OTA_AUTH_ERROR, (char *)"Auth Digest creation error", mbedtls_md_get_name(mbedtls_md_info_from_type(_md_min))); + return; + }; + _replyPacket((char *)"AUTH %s", _nonce.c_str()); + _state = OTA_WAITAUTH; + return; + }; + _replyPacket((char *)"OK"); + + _ota_ip = _udp_ota.remoteIP(); + _state = OTA_RUNUPDATE; +}; + +String ArduinoOTAClass::_hexDigest(const mbedtls_md_info_t * t, String & str) { + unsigned char buff[MBEDTLS_MD_MAX_SIZE]; + mbedtls_md_context_t ctx; + String out = ""; + + mbedtls_md_init(&ctx); + if ( + (mbedtls_md_setup(&ctx, t, 0) == 0) && + (mbedtls_md_update(&ctx, (const unsigned char*)(str.c_str()), str.length()) == 0) && + (mbedtls_md_finish(&ctx, buff) == 0) + ) { + for (int i = 0; i < mbedtls_md_get_size(t); i++) + out += String(buff[i], HEX); + }; + mbedtls_md_free(&ctx); + return out; } -String ArduinoOTAClass::readStringUntil(char end){ - String res = ""; - int value; - while(true){ - value = _udp_ota.read(); - if(value <= 0 || value == end){ - return res; - } - res += (char)value; - } - return res; +void ArduinoOTAClass::_onRxDigestAuthPhase() { + int cmd = parseInt(); + if (cmd != U_AUTH) { + _reportErrorAndAbort(OTA_BEGIN_ERROR, (char *)"Did not get expected AUTH (%d) command: %d", U_AUTH, cmd); + return; + } + + _udp_ota.read(); + String cnonce = readStringUntil(' '); + String response = readStringUntil('\n'); + + if (cnonce.length() != mbedtls_md_get_size(_md_auth_info) || response.length() != mbedtls_md_get_size(_md_auth_info)) { + _reportErrorAndAbort(OTA_AUTH_ERROR, (char *)"auth param fail"); + return; + } + + String challenge = _password + ":" + String(_nonce) + ":" + cnonce; + String result = _hexDigest(_md_auth_info, challenge); + + if (result.length() != mbedtls_md_get_size(_md_auth_info) * 2) { + _reportErrorAndAbort(OTA_AUTH_ERROR, (char *)"Auth Digest error", mbedtls_md_get_name(mbedtls_md_info_from_type(_md_min))); + return; + } + + if (!result.equals(response)) { + _reportErrorAndAbort(OTA_AUTH_ERROR, (char *)"Authentication Failed"); + return; + }; + + _replyPacket((char *)"OK"); + _ota_ip = _udp_ota.remoteIP(); + _state = OTA_RUNUPDATE; +}; + + +void ArduinoOTAClass::_reportErrorAndAbort(ota_error_t error, const char * fmt, ...) { + va_list ap; + char _err[128]; + va_start(ap, fmt); + vsnprintf(_err, sizeof(_err), fmt, ap); + va_end(ap); + + if (_error_callback) + _error_callback(error); + + log_e( "Error: %s", _err); + + _replyPacket(_err); + + _goIdle(); + return; } -void ArduinoOTAClass::_onRx(){ - if (_state == OTA_IDLE) { - int cmd = parseInt(); - if (cmd != U_FLASH && cmd != U_SPIFFS) - return; - _cmd = cmd; - _ota_port = parseInt(); - _size = parseInt(); - _udp_ota.read(); - _md5 = readStringUntil('\n'); - _md5.trim(); - if(_md5.length() != 32){ - log_e("bad md5 length"); - return; - } - - if (_password.length()){ - MD5Builder nonce_md5; - nonce_md5.begin(); - nonce_md5.add(String(micros())); - nonce_md5.calculate(); - _nonce = nonce_md5.toString(); - - _udp_ota.beginPacket(_udp_ota.remoteIP(), _udp_ota.remotePort()); - _udp_ota.printf("AUTH %s", _nonce.c_str()); - _udp_ota.endPacket(); - _state = OTA_WAITAUTH; - return; - } else { - _udp_ota.beginPacket(_udp_ota.remoteIP(), _udp_ota.remotePort()); - _udp_ota.print("OK"); - _udp_ota.endPacket(); - _ota_ip = _udp_ota.remoteIP(); - _state = OTA_RUNUPDATE; - } - } else if (_state == OTA_WAITAUTH) { - int cmd = parseInt(); - if (cmd != U_AUTH) { - log_e("%d was expected. got %d instead", U_AUTH, cmd); - _state = OTA_IDLE; - return; - } - _udp_ota.read(); - String cnonce = readStringUntil(' '); - String response = readStringUntil('\n'); - if (cnonce.length() != 32 || response.length() != 32) { - log_e("auth param fail"); - _state = OTA_IDLE; - return; - } - - String challenge = _password + ":" + String(_nonce) + ":" + cnonce; - MD5Builder _challengemd5; - _challengemd5.begin(); - _challengemd5.add(challenge); - _challengemd5.calculate(); - String result = _challengemd5.toString(); - - if(result.equals(response)){ - _udp_ota.beginPacket(_udp_ota.remoteIP(), _udp_ota.remotePort()); - _udp_ota.print("OK"); - _udp_ota.endPacket(); - _ota_ip = _udp_ota.remoteIP(); - _state = OTA_RUNUPDATE; - } else { - _udp_ota.beginPacket(_udp_ota.remoteIP(), _udp_ota.remotePort()); - _udp_ota.print("Authentication Failed"); - log_w("Authentication Failed"); - _udp_ota.endPacket(); - if (_error_callback) _error_callback(OTA_AUTH_ERROR); - _state = OTA_IDLE; - } - } +void ArduinoOTAClass::_goIdle() { + if (_state == OTA_RUNUPDATE) { + Update.abort(); + }; + if (_md_ctx) { + mbedtls_md_free(_md_ctx); + free(_md_ctx); + _md_ctx = NULL; + } + _state = OTA_IDLE; + } -void ArduinoOTAClass::_runUpdate() { - if (!Update.begin(_size, _cmd)) { +void ArduinoOTAClass::_replyPacket(char * fmt, ...) { + va_list ap; + char buff[128]; + va_start(ap, fmt); + vsnprintf(buff, sizeof(buff), fmt, ap); + va_end(ap); - log_e("Begin ERROR: %s", Update.errorString()); + _udp_ota.beginPacket(_udp_ota.remoteIP(), _udp_ota.remotePort()); + _udp_ota.print(buff); + _udp_ota.endPacket(); +}; - if (_error_callback) { - _error_callback(OTA_BEGIN_ERROR); - } - _state = OTA_IDLE; - return; - } - Update.setMD5(_md5.c_str()); - if (_start_callback) { - _start_callback(); - } - if (_progress_callback) { - _progress_callback(0, _size); +char * ArduinoOTAClass::_handleUpdate(WiFiClient & client) { + uint32_t written = 0, total = 0, timeouts = 0, errs = 0; + + while (!Update.isFinished() && client.connected()) { + size_t waited = _ota_timeout; + size_t available = client.available(); + + while (!available && waited) { + delay(1 /* miliseconds */); + waited--; + available = client.available(); } + if (waited == 0) { + log_i("ReTry[%u]: %u", waited, timeouts); - WiFiClient client; - if (!client.connect(_ota_ip, _ota_port)) { - if (_error_callback) { - _error_callback(OTA_CONNECT_ERROR); - } - _state = OTA_IDLE; + if (timeouts++ > 3) + return (char *)"Receive timeout"; + + if (!client.printf("%u\n", written)) + return (char *)"failed to return bytes written"; + + continue; } + if (!available) + return (char *)"No Data"; - uint32_t written = 0, total = 0, tried = 0; - - while (!Update.isFinished() && client.connected()) { - size_t waited = _ota_timeout; - size_t available = client.available(); - while (!available && waited){ - delay(1); - waited -=1 ; - available = client.available(); - } - if (!waited){ - if(written && tried++ < 3){ - log_i("Try[%u]: %u", tried, written); - if(!client.printf("%u", written)){ - log_e("failed to respond"); - _state = OTA_IDLE; - break; - } - continue; - } - log_e("Receive Failed"); - if (_error_callback) { - _error_callback(OTA_RECEIVE_ERROR); - } - _state = OTA_IDLE; - Update.abort(); - return; - } - if(!available){ - log_e("No Data: %u", waited); - _state = OTA_IDLE; - break; - } - tried = 0; - static uint8_t buf[1460]; - if(available > 1460){ - available = 1460; - } - size_t r = client.read(buf, available); - if(r != available){ - log_w("didn't read enough! %u != %u", r, available); - } - - written = Update.write(buf, r); - if (written > 0) { - if(written != r){ - log_w("didn't write enough! %u != %u", written, r); - } - if(!client.printf("%u", written)){ - log_w("failed to respond"); - } - total += written; - if(_progress_callback) { - _progress_callback(total, _size); - } - } else { - log_e("Write ERROR: %s", Update.errorString()); - } + timeouts = 0; + static uint8_t buf[1460]; + if (available > 1460) { + available = 1460; + } + size_t r = client.read(buf, available); + if (r == -1 && errs < 3) { + errs++; + } + if (r != available) { + log_w("Didn't read enough! %u != %u", r, available); } - if (Update.end()) { - client.print("OK"); - client.stop(); - delay(10); - if (_end_callback) { - _end_callback(); - } - if(_rebootOnSuccess){ - //let serial/network finish tasks that might be given in _end_callback - delay(100); - ESP.restart(); - } - } else { - if (_error_callback) { - _error_callback(OTA_END_ERROR); - } - Update.printError(client); - client.stop(); - delay(10); - log_e("Update ERROR: %s", Update.errorString()); - _state = OTA_IDLE; + written = Update.write(buf, r); + if (written <= 0 || written != r) + return (char *)"Firmware Write error"; + if (written != r) + return (char *)"Incomplete Firware Write"; + + if ((mbedtls_md_update(_md_ctx, buf, written)) != 0) + return (char *)"Digest update failed"; + + total += written; + + if (_progress_callback) { + _progress_callback(total, _size); } + + if (!client.printf("%u\n", written)) + return (char *)"failed to return bytes written"; + } // while we're connected and there is data available. + return NULL; +} + +void ArduinoOTAClass::_runUpdate() { + + if (!Update.begin(_size, _cmd)) { + _reportErrorAndAbort(OTA_BEGIN_ERROR, (char *)"Begin ERROR: %s\n", Update.errorString()); + return; + } + + if (_start_callback) { + _start_callback(); + } + + if (_progress_callback) { + _progress_callback(0, _size); + } + mbedtls_md_starts(_md_ctx); + + WiFiClient client; + if (!client.connect(_ota_ip, _ota_port)) { + _reportErrorAndAbort(OTA_CONNECT_ERROR, (char *)"Connect ERROR: %s:%d %s\n", String(_ota_ip).c_str(), _ota_port, Update.errorString()); + return; + } + + char * err = _handleUpdate(client); + if (err) + printf("\nHandling error: %s\n", err); + else + printf("OK!"); + + if (err) + client.print(err); + else + client.println("OK"); + + client.stop(); + delay(10); + + if (err) { + _reportErrorAndAbort(OTA_RECEIVE_ERROR, err); + return; + }; + + if (!Update.end()) { + _reportErrorAndAbort(OTA_END_ERROR, "Could not finalize update"); + return; + } + + unsigned char buff[MBEDTLS_MD_MAX_SIZE]; + int ret; + if ((ret = mbedtls_md_finish(_md_ctx, buff)) != 0) { + _reportErrorAndAbort(OTA_END_ERROR, "Outer digest calculation failed: %x", -ret); + return; + } + + if (bcmp(buff, _md_buff, mbedtls_md_get_size(_md_info)) != 0) { + _reportErrorAndAbort(OTA_END_ERROR, "Outer digest mismatch, rejecting"); + return; + }; + + log_d("OTA Outer Digest matched."); + + if (_end_callback) { + _end_callback(); + } + + if (_approve_reboot_callback && !(_approve_reboot_callback())) { + _replyPacket((char *)"No permission to activate from current firmware"); + _goIdle(); + return; + }; + + if (!Update.activate()) { + _reportErrorAndAbort(OTA_END_ERROR, "Failed to activate"); + return; + }; + + _goIdle(); + + if (_rebootOnSuccess) { + //let serial/network finish tasks that might be given in _end_callback + delay(100); + ESP.restart(); + } + return; } void ArduinoOTAClass::end() { - _initialized = false; - _udp_ota.stop(); - if(_mdnsEnabled){ - MDNS.end(); - } - _state = OTA_IDLE; - log_i("OTA server stopped."); + _initialized = false; + _goIdle(); + _udp_ota.stop(); + if (_mdnsEnabled) { + MDNS.end(); + } + log_i("OTA server stopped."); } void ArduinoOTAClass::handle() { - if (!_initialized) { - return; - } - if (_state == OTA_RUNUPDATE) { - _runUpdate(); - _state = OTA_IDLE; - } - if(_udp_ota.parsePacket()){ - _onRx(); - } - _udp_ota.flush(); // always flush, even zero length packets must be flushed. + if (!_initialized) { + return; + } + switch (_state) { + case OTA_RUNUPDATE: + _runUpdate(); + break; + case OTA_WAITAUTH: + if (_udp_ota.parsePacket()) + _onRxDigestAuthPhase(); + break; + case OTA_IDLE: + if (_udp_ota.parsePacket()) + _onRxHeaderPhase(); + break; + default: + _reportErrorAndAbort(OTA_BEGIN_ERROR, "Confused."); + break; + } + _udp_ota.flush(); // always flush, even zero length packets must be flushed. } int ArduinoOTAClass::getCommand() { - return _cmd; + return _cmd; } void ArduinoOTAClass::setTimeout(int timeoutInMillis) { - _ota_timeout = timeoutInMillis; + _ota_timeout = timeoutInMillis; } #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_ARDUINOOTA) diff --git a/libraries/ArduinoOTA/src/ArduinoOTA.h b/libraries/ArduinoOTA/src/ArduinoOTA.h index db0ead631db..5a9e3bf2ed3 100644 --- a/libraries/ArduinoOTA/src/ArduinoOTA.h +++ b/libraries/ArduinoOTA/src/ArduinoOTA.h @@ -1,10 +1,12 @@ -#ifndef __ARDUINO_OTA_H -#define __ARDUINO_OTA_H +#ifndef __SECURE_ARDUINO_OTA_H +#define __SECURE_ARDUINO_OTA_H #include #include #include "Update.h" +#include + #define INT_BUFFER_SIZE 16 typedef enum { @@ -24,9 +26,10 @@ typedef enum { class ArduinoOTAClass { public: - typedef std::function THandlerFunction; + typedef std::function THandlerFunction; typedef std::function THandlerFunction_Error; typedef std::function THandlerFunction_Progress; + typedef std::function THandlerFunction_ApproveReboot; ArduinoOTAClass(); ~ArduinoOTAClass(); @@ -39,7 +42,7 @@ class ArduinoOTAClass String getHostname(); //Sets the password that will be required for OTA. Default NULL - ArduinoOTAClass& setPassword(const char *password); + ArduinoOTAClass& setPassword(const char *password, const char * digest_type = "MD5"); //Sets the password as above but in the form MD5(password). Default NULL ArduinoOTAClass& setPasswordHash(const char *password); @@ -62,6 +65,12 @@ class ArduinoOTAClass //This callback will be called when OTA is receiving data ArduinoOTAClass& onProgress(THandlerFunction_Progress fn); + // This callback will be called just before rebooting; giving + // a last chance to abort the process. + ArduinoOTAClass& approveReboot(THandlerFunction_ApproveReboot fn); + + ArduinoOTAClass& setProcessor(UpdateProcessor * processor); + //Starts the ArduinoOTA service void begin(); @@ -76,6 +85,26 @@ class ArduinoOTAClass void setTimeout(int timeoutInMillis); + int setMinimalDigestLevel(const mbedtls_md_info_t * m) { + if (m == NULL) + return -1; + _md_min = mbedtls_md_get_type(m); + return 0; + }; + + int setMinimalDigestLevel(String str) { + return setMinimalDigestLevel(str.c_str()); + }; + + int setMinimalDigestLevel(const char * str) { + return setMinimalDigestLevel(mbedtls_md_info_from_string(str)); + }; + + int setMinimalDigestLevel(mbedtls_md_type_t min) { + return setMinimalDigestLevel(mbedtls_md_info_from_type(min)); + }; + + private: int _port; String _password; @@ -91,15 +120,34 @@ class ArduinoOTAClass int _ota_port; int _ota_timeout; IPAddress _ota_ip; - String _md5; + + const mbedtls_md_info_t *_md_info, *_md_auth_info; + mbedtls_md_context_t * _md_ctx; + unsigned char _md_buff[MBEDTLS_MD_MAX_SIZE]; + mbedtls_md_type_t _md_min; + String _digestAsHexString; THandlerFunction _start_callback; THandlerFunction _end_callback; THandlerFunction_Error _error_callback; THandlerFunction_Progress _progress_callback; + THandlerFunction_ApproveReboot _approve_reboot_callback; + + void _reportErrorAndAbort(ota_error_t error, const char * errmsg, ...); + void _goIdle(); + void _replyPacket(char * msg, ...); + + String _hexDigest(const mbedtls_md_info_t * t, String &str); + void _onRxHeaderPhase(); + char * _processsOldStyleHeader(); + char * _processsHeader(); + + void _onRxDigestAuthPhase(); void _runUpdate(void); - void _onRx(void); + + char * _handleUpdate(WiFiClient & client); + int parseInt(void); String readStringUntil(char end); }; @@ -108,4 +156,4 @@ class ArduinoOTAClass extern ArduinoOTAClass ArduinoOTA; #endif -#endif /* __ARDUINO_OTA_H */ \ No newline at end of file +#endif /* __ARDUINO_OTA_H */ diff --git a/libraries/Update/src/Update.h b/libraries/Update/src/Update.h index 9a46a784870..b85c02a6b5a 100644 --- a/libraries/Update/src/Update.h +++ b/libraries/Update/src/Update.h @@ -1,11 +1,12 @@ -#ifndef ESP8266UPDATER_H -#define ESP8266UPDATER_H +#ifndef ESP32_UPDATER_H +#define ESP32_UPDATER_H #include -#include #include #include "esp_partition.h" +#include "UpdateProcessor.h" + #define UPDATE_ERROR_OK (0) #define UPDATE_ERROR_WRITE (1) #define UPDATE_ERROR_ERASE (2) @@ -13,35 +14,40 @@ #define UPDATE_ERROR_SPACE (4) #define UPDATE_ERROR_SIZE (5) #define UPDATE_ERROR_STREAM (6) -#define UPDATE_ERROR_MD5 (7) +#define UPDATE_ERROR_CHECKSUM (7) #define UPDATE_ERROR_MAGIC_BYTE (8) #define UPDATE_ERROR_ACTIVATE (9) #define UPDATE_ERROR_NO_PARTITION (10) #define UPDATE_ERROR_BAD_ARGUMENT (11) #define UPDATE_ERROR_ABORT (12) -#define UPDATE_SIZE_UNKNOWN 0xFFFFFFFF - -#define U_FLASH 0 -#define U_SPIFFS 100 -#define U_AUTH 200 +/* to keep the API backward compatible */ +#define UPDATE_ERROR_MD5 UPDATE_ERROR_CHECKSUM class UpdateClass { public: typedef std::function THandlerFunction_Progress; + typedef std::function THandlerFunction_ApproveOTA; - UpdateClass(); - + UpdateClass(UpdateProcessor * processor = NULL); + ~UpdateClass(); /* This callback will be called when Update is receiving data */ UpdateClass& onProgress(THandlerFunction_Progress fn); + UpdateClass& approveReboot(THandlerFunction_ApproveOTA fn); + + /* Specify an optional processor - that will check, validate or + * otherwise process the data. If none is specified; the default + * (straight write through) will be used. + */ + void setProcessor(UpdateProcessor * processor); /* Call this to check the space needed for the update Will return false if there is not enough space */ - bool begin(size_t size=UPDATE_SIZE_UNKNOWN, int command = U_FLASH, int ledPin = -1, uint8_t ledOn = LOW); + bool begin(size_t size = UPDATE_SIZE_UNKNOWN, int command = U_FLASH, int ledPin = -1, uint8_t ledOn = LOW); /* Writes a buffer to the flash and increments the address @@ -56,20 +62,34 @@ class UpdateClass { Should be equal to the remaining bytes when called Usable for slow streams like Serial */ - size_t writeStream(Stream &data); + size_t writeStream(Stream & data); /* - If all bytes are written - this call will write the config to eboot - and return true + Template to write from objects that expose + available() and read(uint8_t*, size_t) methods + faster than the writeStream method + writes only what is available + */ + template + size_t write(T & data); + + /* + If all bytes are written, any checksums match; then this call will return true + If there is already an update running but is not finished and !evenIfRemaining or there is an error + this will clear everything and return false the last error is available through getError() + evenIfRemaining is helpfull when you update without knowing the final size first */ bool end(bool evenIfRemaining = false); + /* this call will write the config to eboot + and return true + */ + bool activate(); /* Aborts the running update */ @@ -78,76 +98,39 @@ class UpdateClass { /* Prints the last error to an output stream */ - void printError(Stream &out); + void printError(Stream & out); const char * errorString(); /* - sets the expected MD5 for the firmware (hexString) - */ - bool setMD5(const char * expected_md5); - - /* - returns the MD5 String of the sucessfully ended firmware - */ - String md5String(void){ return _md5.toString(); } - - /* - populated the result with the md5 bytes of the sucessfully ended firmware + Helpers */ - void md5(uint8_t * result){ return _md5.getBytes(result); } - - //Helpers - uint8_t getError(){ return _error; } - void clearError(){ _error = UPDATE_ERROR_OK; } - bool hasError(){ return _error != UPDATE_ERROR_OK; } - bool isRunning(){ return _size > 0; } - bool isFinished(){ return _progress == _size; } - size_t size(){ return _size; } - size_t progress(){ return _progress; } - size_t remaining(){ return _size - _progress; } - - /* - Template to write from objects that expose - available() and read(uint8_t*, size_t) methods - faster than the writeStream method - writes only what is available - */ - template - size_t write(T &data){ - size_t written = 0; - if (hasError() || !isRunning()) - return 0; - - size_t available = data.available(); - while(available) { - if(_bufferLen + available > remaining()){ - available = remaining() - _bufferLen; - } - if(_bufferLen + available > 4096) { - size_t toBuff = 4096 - _bufferLen; - data.read(_buffer + _bufferLen, toBuff); - _bufferLen += toBuff; - if(!_writeBuffer()) - return written; - written += toBuff; - } else { - data.read(_buffer + _bufferLen, available); - _bufferLen += available; - written += available; - if(_bufferLen == remaining()) { - if(!_writeBuffer()) { - return written; - } - } - } - if(remaining() == 0) - return written; - available = data.available(); - } - return written; + uint8_t getError() { + return _error; + } + void clearError() { + _error = UPDATE_ERROR_OK; + } + bool hasError() { + return _error != UPDATE_ERROR_OK; + } + bool isRunning() { + return _size > 0; + } + bool isFinished() { + return _progress == _size; + } + size_t size() { + return _size; + } + size_t progress() { + return _progress; + } + size_t remaining() { + return _size - _progress; } + /* check if there is a firmware on the other OTA partition that you can bootinto */ @@ -158,29 +141,30 @@ class UpdateClass { bool rollBack(); private: + UpdateProcessor *_processor; void _reset(); void _abort(uint8_t err); bool _writeBuffer(); - bool _verifyHeader(uint8_t data); - bool _verifyEnd(); - + bool _verifyHeader(uint8_t * buffer, size_t len); uint8_t _error; uint8_t *_buffer; size_t _bufferLen; size_t _size; THandlerFunction_Progress _progress_callback; - uint32_t _progress; + + uint32_t _progress, _progress_flash; uint32_t _command; const esp_partition_t* _partition; - - String _target_md5; - MD5Builder _md5; + bool _post_header; + String _md; int _ledPin; uint8_t _ledOn; }; +#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_UPDATE) extern UpdateClass Update; +#endif #endif diff --git a/libraries/Update/src/UpdateProcessor.cpp b/libraries/Update/src/UpdateProcessor.cpp new file mode 100644 index 00000000000..e57a42c7a8e --- /dev/null +++ b/libraries/Update/src/UpdateProcessor.cpp @@ -0,0 +1,38 @@ + +#include "Arduino.h" +#include "esp_spi_flash.h" +#include "esp_ota_ops.h" +#include "esp_image_format.h" +#include "esp_partition.h" +#include "esp_spi_flash.h" + +#include "Updateprocessor.h" + +/* We have four formats; this handler deals with the original format: + + 1) original 'raw' package. + + Starts with 0xE7 / ESP_IMAGE_HEADER_MAGIC. + + Which has no checksum or other protection (beyond the size check and the check of the first byte). + + The EC, S/MIME and RFC3161 versons have thier own processors. + +*/ + + +UpdateProcessor::secure_update_processor_err_t UpdateProcessorLegacy::process_header(uint32_t *command, uint8_t* buffer, size_t *len) { + if ((*command) == U_SPIFFS) + return UpdateProcessor::secure_update_processor_OK; + + if ((*command) == U_FLASH) { + if (buffer[0] == ESP_IMAGE_HEADER_MAGIC) { + log_d("Valid magic at start of flash header"); + return UpdateProcessor::secure_update_processor_OK; + }; + log_e("Missing ESP_IMAGE_HEADER_MAGIC"); + }; + log_e("Invalid command 0x%04x ", *command); + + return UpdateProcessor::secure_update_processor_ERROR; +}; diff --git a/libraries/Update/src/UpdateProcessor.h b/libraries/Update/src/UpdateProcessor.h new file mode 100644 index 00000000000..cf294e5f86a --- /dev/null +++ b/libraries/Update/src/UpdateProcessor.h @@ -0,0 +1,51 @@ +#ifndef _UPDATE_PROCESSOR +#define _UPDATE_PROCESSOR + +#include +#include +#include + +#include "mbedtls-ts-addons/ts.h" + +#define UPDATE_SIZE_UNKNOWN 0xFFFFFFFF + +// Potential commands +#ifndef U_FLASH +#define U_FLASH (0) +#endif + +#ifndef U_SPIFFS +#define U_SPIFFS (100) +#endif + +#ifndef U_AUTH +#define U_AUTH (200) +#endif + +class UpdateProcessor { + public: + typedef enum secure_update_processor_err_t { + secure_update_processor_OK = 0, + secure_update_processor_AGAIN, // cannot (yet) act; not enough data passed + secure_update_processor_DECLINED, + secure_update_processor_ERROR, + } secure_update_processor_err_t; + virtual ~UpdateProcessor() {}; + virtual void reset(); + virtual secure_update_processor_err_t process_header(uint32_t *command, uint8_t * buffer, size_t *len); + virtual secure_update_processor_err_t process_payload(uint8_t * buff, size_t *len); + virtual secure_update_processor_err_t process_end(); +}; + +class UpdateProcessorLegacy : public UpdateProcessor { + public: + void reset() {}; + secure_update_processor_err_t process_header(uint32_t *command, uint8_t * buffer, size_t *len); + secure_update_processor_err_t process_payload(uint8_t * buff, size_t *len) { + return secure_update_processor_OK; + } + secure_update_processor_err_t process_end() { + return secure_update_processor_OK; + }; +}; +#endif diff --git a/libraries/Update/src/UpdateProcessorRFC3161.cpp b/libraries/Update/src/UpdateProcessorRFC3161.cpp new file mode 100644 index 00000000000..caed3c61ac1 --- /dev/null +++ b/libraries/Update/src/UpdateProcessorRFC3161.cpp @@ -0,0 +1,383 @@ + +#include "Arduino.h" +#include "esp_spi_flash.h" +#include "esp_ota_ops.h" +#include "esp_image_format.h" +#include "esp_partition.h" +#include "esp_spi_flash.h" + +#include "UpdateProcessorRFC3161.h" + +/* We have three formats; + + 1) original 'raw' package. + + Starts with 0xE7 / ESP_IMAGE_HEADER_MAGIC. + + Which has no checksum or other protection (beyond the size check and the check of the first byte). + + 2) The experimental format; starting with 'RedWaxEU\r\n' followed by a raw ECDSAignature; against + a list of hardcoded public keys. + + 3) A more interoperable that allows for both a timestamp and a signature; see + https://tools.ietf.org/id/draft-moran-suit-architecture-02.html for why both + are needed (Downgrades). + + It starts with RedWax/A.BB; where A=1 and B can be any digits. + + It may be followed by key-value pairs. This header line is ended by a '\n' + and can not be longer than 128 bytes (\n included). + + For version 1.XX it is immediately followed by a DER encoded + RFC3161 timestamp/signature 'reply'. And this is followed by + the original 'raw' package - i.e as per above '1'. + + This is validated both for SPIFFS(firmware) as wel as for FLASH (code). + + BUT: (and this caveat applies to all methods) -- a corrupted or nefarious + upload will be detected; but will only in the case of a OTA FLASH update + lead to that new code not getting activated. For a SPIFFS update that is + not an option (as you are overwriting the 'real' one). So in this case + the calling code may want to wipe the SPIFFs filesystem. +*/ + +#define REDWAX_MAGIC_HEADER "RedWax/1." + +UpdateProcessorRFC3161::UpdateProcessorRFC3161(UpdateProcessor * nxt) + : _state(INIT) + , _rfc3161(NULL) + , _rfc3161_at(0) + , _rfc3161_len(0) + , _progress_flash(0) + , _next(nxt) + , _md_info(NULL) + , _md_ctx(NULL) + , _payload_len(0) + , _trustChain(NULL) + , _legacyAllowed(false) +{ + // we need to hook this in - as the legacy processor does the actual + // burning of the firmware. + // + if (_next == NULL) + _next = new UpdateProcessorLegacy(); +}; + +static void _freeChain(mbedtls_x509_crt *c) { + while (c) { + mbedtls_x509_crt * p = c; + c = c->next; + free(p); + }; +} + +UpdateProcessorRFC3161:: ~UpdateProcessorRFC3161() { + reset(); + delete _next; + _freeChain(_trustChain); +}; + +int UpdateProcessorRFC3161::addTrustedCertAsDER(const unsigned char * der, size_t len) { + int ret; + mbedtls_x509_crt * crt; + + if ((crt = (mbedtls_x509_crt*)malloc(sizeof(mbedtls_x509_crt))) == NULL) + return MBEDTLS_ERR_X509_ALLOC_FAILED; + + mbedtls_x509_crt_init(crt); + + if ((ret = mbedtls_x509_crt_parse(crt, der, len)) != 0) { + free(crt); + return ret; + } + crt->next = _trustChain; + _trustChain = crt; + return 0; +} + +int UpdateProcessorRFC3161::setTrustedCerts(mbedtls_x509_crt * trustChain) { + _freeChain(_trustChain); + _trustChain = trustChain; + return 0; +} + +int UpdateProcessorRFC3161::setAllowLegacyUploads(bool legacyAllowed) { + _legacyAllowed = legacyAllowed; + return 0; +} + +void UpdateProcessorRFC3161::reset() { + log_d("RESET"); + if (_rfc3161) + free(_rfc3161); + + if (_md_ctx) { + mbedtls_md_free(_md_ctx); + free(_md_ctx); + _md_ctx = NULL; + } + + _rfc3161 = NULL; + _rfc3161_len = 0; + _rfc3161_at = 0; + + _payload_len = 0; + _state = INIT; +}; + +UpdateProcessor::secure_update_processor_err_t UpdateProcessorRFC3161::process_header(uint32_t *command, uint8_t * buffer, size_t *len) { + unsigned char * p; + uint32_t results = 0; + size_t l; + + switch (_state) { + case INIT: + if (*len < 128) { + log_d("Not enough bytes yet"); + return secure_update_processor_AGAIN; + }; + + if ((buffer[0] != REDWAX_MAGIC_HEADER[0]) || (bcmp(REDWAX_MAGIC_HEADER, buffer, sizeof(REDWAX_MAGIC_HEADER) - 1))) { + if (_legacyAllowed && _next) + return _next->process_header(command, buffer, len); + return secure_update_processor_ERROR; + }; + + p = (unsigned char *)memchr(buffer, '\n', *len); + + if (p == NULL) { + log_e("No EOL fo%s: %d\n\n", REDWAX_MAGIC_HEADER, *len); + return secure_update_processor_ERROR; + }; + + if (1) { + *p = '\0'; + char *q = index((char *)buffer, ' '); + if (q == NULL) q = (char *)buffer; + log_d("Ignoring params: %s", q); + }; + + // what now follows is the DER encoded rfc3161 blob. + // 30 (sequence) 82 (elems) 09 07 length + p++; + if (p[0] != 0x30 || p[1] != 0x82) { + log_e("No TSR ? %x %x\n\n", p[0], p[1]); + return secure_update_processor_ERROR; + }; + _rfc3161_len = (p[2] << 8) + p[3] + 4 /* header */; + + assert(_rfc3161 == NULL); + if (_rfc3161_len > 16 * 1024 || (_rfc3161 = (unsigned char *)malloc(_rfc3161_len)) == NULL) { + log_e("Size problems %x %x -> %d\n\n", p[2], p[3], _rfc3161_len); + return secure_update_processor_ERROR; + }; + + // Skip over our header. + *len -= (p - buffer); + bcopy(p, buffer, *len); + _rfc3161_at = 0; + + _state = RFC; + log_d("RFC 3161 signed payload"); + return secure_update_processor_AGAIN; + break; + + case RFC: + // are we still working on the pre-amble; or actually + // aready reading the real thing ? + // + l = *len; + if (l > _rfc3161_len - _rfc3161_at) + l = _rfc3161_len - _rfc3161_at; + + bcopy( buffer, _rfc3161 + _rfc3161_at, l); + _rfc3161_at += l; + *len -= l; + + // move anything that was unprocessed in the buffer (if any) + // + if (_rfc3161_at >= _rfc3161_len && *len) { + bcopy( buffer + l, buffer, *len); + }; + + if (_rfc3161_at != _rfc3161_len) + return secure_update_processor_AGAIN; + + // Validate the RFC 3161 signature package. And when valid, use + // the signed digest (typically a SHA256) to validate the file + // that follows. + // + log_d("Processing RFC 3161 signed payload"); + + bzero(&_reply, sizeof(_reply)); + + if ((mbedtls_x509_ts_reply_parse_der((const unsigned char **)&_rfc3161, &_rfc3161_len, &_reply)) != 0) { + // _abort(UPDATE_ERROR_CHECKSUM); + log_e("RFC 3161 signature invalid."); + + return secure_update_processor_ERROR; + }; + + if (_reply.ts_info.digest_type < MBEDTLS_MD_SHA256) { + Serial.printf("Digest %s not accepatble", mbedtls_md_get_name(mbedtls_md_info_from_type(_reply.ts_info.digest_type))); + return secure_update_processor_ERROR; + } + log_d("Processed RFC 3161 signed payload"); + + if ((_md_info = mbedtls_md_info_from_type(_reply.ts_info.digest_type)) == NULL || + (_md_ctx = (mbedtls_md_context_t*)malloc(sizeof(*_md_ctx))) == NULL + ) { + log_e("Invalid/memory issue digest."); + return secure_update_processor_ERROR; + }; + log_i("Processing plaintext with %s specified RFC3161 digest.", mbedtls_md_get_name(_md_info)); + + mbedtls_md_init(_md_ctx); + if ( + (mbedtls_md_setup(_md_ctx, _md_info, 0) != 0) || + (mbedtls_md_starts(_md_ctx) != 0) + ) { + log_e("Setup issue digest."); + return secure_update_processor_ERROR; + }; + + log_d("RFC3161 signature verified."); + + if (!_trustChain) { + log_e("No chain to validate RFC3161 signature. Aborting."); + return secure_update_processor_ERROR; + }; + +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG + { + log_d("Signatures in the trust chain:"); + for (mbedtls_x509_crt * c = _trustChain; c; c = c->next) { + char buf[1024 * 2]; + mbedtls_x509_crt_info(buf, sizeof(buf), " - ", c); + log_d(" %s", buf); + }; + + log_d("Signatures in the RFC3161 wrapper:"); + for (mbedtls_x509_crt * c = _reply.chain; c; c = c->next) { + char buf[1024 * 2]; + mbedtls_x509_crt_info(buf, sizeof(buf), " - ", c); + log_d(" %s", buf); + }; + } +#endif + if (mbedtls_x509_crt_verify(_reply.chain, _trustChain, NULL /* no CRL */, NULL /* any name fine */, &results, NULL, NULL) != 0) { + log_e("RFC3161 signature could not be validated against the chain. Aborting."); + return secure_update_processor_ERROR; + } + log_i("RFC3161 signature on timestamp and payload digest verified."); + + _state = POST; + return secure_update_processor_AGAIN; + break; + + case POST: + // we're just a wrapper/prefix around the old format; so at + // this point - hand off. + // + log_d("RFC3161: processing payload."); + return _next->process_header(command, buffer, len); + break; + default: + return secure_update_processor_ERROR; + break; + }; + // _abort(UPDATE_ERROR_MAGIC_BYTE); + return secure_update_processor_ERROR; +}; + +UpdateProcessor::secure_update_processor_err_t UpdateProcessorRFC3161::process_payload(uint8_t * buffer, size_t *len) { + // calculate the digest using the digest type specified in the RFC 3161 TS Info section. + // + // assuming we unwisely allow for this. + if (_rfc3161 == NULL) { + if (_legacyAllowed && _next) + return _next->process_payload(buffer, len); + log_e("RFC3161: cannot process payload - no signature."); + return secure_update_processor_ERROR; + }; + + if (mbedtls_md_update(_md_ctx, buffer, *len)) { + log_e("Failed to update digest."); + return secure_update_processor_ERROR; + } + _payload_len += *len; + +#if 0 + static size_t _progress_flash = 0; + Serial.printf("---\n"); + for (int j = 0; j * 16 < *len; j++) { + Serial.printf("\n%08x ", _progress_flash + j * 16); + for (int i = 0; i < 16; i++) { + if (j * 16 < *len) + Serial.printf("%02x ", buffer[j * 16 + i ]); + else + Serial.printf("-- "); + if (i == 7) + Serial.printf(" "); + }; + Serial.printf(" |"); + for (int i = 0; i < 16; i++) { + char c = buffer[ j * 16 + i ]; + Serial.printf("%c", c >= 32 && c < 127 ? c : '.' ); + }; + Serial.printf("|"); + Serial.flush(); + _progress_flash += *len; + } + Serial.println("\n"); +#endif + + return UpdateProcessor::secure_update_processor_OK; +}; + + +UpdateProcessor::secure_update_processor_err_t UpdateProcessorRFC3161::process_end() { + unsigned char buff[MBEDTLS_MD_MAX_SIZE]; + log_d("RFC3161 Finalizing payload digest"); + + // assuming it is not mandatory ?! + if (_rfc3161 == NULL && _legacyAllowed) { + if (_legacyAllowed && _next) + return _next->process_end(); + log_e("RFC3161 - cannot finalize, no signature."); + return secure_update_processor_ERROR; + } + + // Verify the digest with the one from the RFC 3161 TS Info section. + if (mbedtls_md_finish(_md_ctx, buff)) { + log_e("Failed to finalize digest."); + return secure_update_processor_ERROR; + } + +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG + String out = ""; + for (int i = 0; i < mbedtls_md_get_size(_md_info); i++) + out += String(buff[i], HEX); + log_d("Payload calculated %s Digest %d:%s", mbedtls_md_get_name(_md_info), mbedtls_md_get_size(_md_info), out.c_str()); + + String out2 = ""; + for (int i = 0; i < mbedtls_md_get_size(_md_info); i++) + out2 += String(_reply.ts_info.payload_digest[i], HEX); + log_d("RFC3161 Receveived %s Digest %d:%s", mbedtls_md_get_name(_md_info), mbedtls_md_get_size(_md_info), out2.c_str()); +#endif + + int res = memcmp(_reply.ts_info.payload_digest, buff, mbedtls_md_get_size(_md_info)); + + mbedtls_md_free(_md_ctx); + free(_md_ctx); + _md_ctx = NULL; + + // compare this with the digest we got from the signed TSInfo + if (res) { + log_e("RFC3161 Payload digest does NOT match signed digest."); + return UpdateProcessor::secure_update_processor_ERROR; + } + + log_i(" RFC3161 Payload digest matches signed digest."); + return UpdateProcessor::secure_update_processor_OK; +}; diff --git a/libraries/Update/src/UpdateProcessorRFC3161.h b/libraries/Update/src/UpdateProcessorRFC3161.h new file mode 100644 index 00000000000..469599420c6 --- /dev/null +++ b/libraries/Update/src/UpdateProcessorRFC3161.h @@ -0,0 +1,37 @@ +#ifndef _H_RFC3161_UPDATE_PROCESSOR +#define _H_RFC3161_UPDATE_PROCESSOR + +#include +#include +#include +#include + +#include "UpdateProcessor.h" + +class UpdateProcessorRFC3161 : public UpdateProcessor { + public: + UpdateProcessorRFC3161(UpdateProcessor * chain = NULL); + ~UpdateProcessorRFC3161(); + void reset(); + secure_update_processor_err_t process_header(uint32_t *command, uint8_t * buffer, size_t *len); + secure_update_processor_err_t process_payload(uint8_t * buff, size_t *len); + secure_update_processor_err_t process_end(); + + int addTrustedCertAsDER(const unsigned char * der, size_t len); + int setTrustedCerts(mbedtls_x509_crt * trustChain); + int setAllowLegacyUploads(bool legacyAllowed); + private: + enum { INIT, RFC, POST } _state; + unsigned char * _rfc3161; + size_t _rfc3161_at; + size_t _rfc3161_len; + size_t _progress_flash; + UpdateProcessor *_next; + const mbedtls_md_info_t *_md_info; + mbedtls_md_context_t * _md_ctx; + size_t _payload_len = 0; + mbedtls_ts_reply _reply; + mbedtls_x509_crt * _trustChain; + bool _legacyAllowed; +}; +#endif diff --git a/libraries/Update/src/Updater.cpp b/libraries/Update/src/Updater.cpp index cfa28827e96..745855b132f 100644 --- a/libraries/Update/src/Updater.cpp +++ b/libraries/Update/src/Updater.cpp @@ -4,367 +4,483 @@ #include "esp_ota_ops.h" #include "esp_image_format.h" -static const char * _err2str(uint8_t _error){ - if(_error == UPDATE_ERROR_OK){ - return ("No Error"); - } else if(_error == UPDATE_ERROR_WRITE){ - return ("Flash Write Failed"); - } else if(_error == UPDATE_ERROR_ERASE){ - return ("Flash Erase Failed"); - } else if(_error == UPDATE_ERROR_READ){ - return ("Flash Read Failed"); - } else if(_error == UPDATE_ERROR_SPACE){ - return ("Not Enough Space"); - } else if(_error == UPDATE_ERROR_SIZE){ - return ("Bad Size Given"); - } else if(_error == UPDATE_ERROR_STREAM){ - return ("Stream Read Timeout"); - } else if(_error == UPDATE_ERROR_MD5){ - return ("MD5 Check Failed"); - } else if(_error == UPDATE_ERROR_MAGIC_BYTE){ - return ("Wrong Magic Byte"); - } else if(_error == UPDATE_ERROR_ACTIVATE){ - return ("Could Not Activate The Firmware"); - } else if(_error == UPDATE_ERROR_NO_PARTITION){ - return ("Partition Could Not be Found"); - } else if(_error == UPDATE_ERROR_BAD_ARGUMENT){ - return ("Bad Argument"); - } else if(_error == UPDATE_ERROR_ABORT){ - return ("Aborted"); - } - return ("UNKNOWN"); +static const char * _err2str(uint8_t _error) { + if (_error == UPDATE_ERROR_OK) { + return ("No Error"); + } else if (_error == UPDATE_ERROR_WRITE) { + return ("Flash Write Failed"); + } else if (_error == UPDATE_ERROR_ERASE) { + return ("Flash Erase Failed"); + } else if (_error == UPDATE_ERROR_READ) { + return ("Flash Read Failed"); + } else if (_error == UPDATE_ERROR_SPACE) { + return ("Not Enough Space"); + } else if (_error == UPDATE_ERROR_SIZE) { + return ("Bad Size Given"); + } else if (_error == UPDATE_ERROR_STREAM) { + return ("Stream Read Timeout"); + } else if (_error == UPDATE_ERROR_MAGIC_BYTE) { + return ("Wrong Magic Byte"); + } else if (_error == UPDATE_ERROR_ACTIVATE) { + return ("Could Not Activate The Firmware"); + } else if (_error == UPDATE_ERROR_NO_PARTITION) { + return ("Partition Could Not be Found"); + } else if (_error == UPDATE_ERROR_BAD_ARGUMENT) { + return ("Bad Argument"); + } else if (_error == UPDATE_ERROR_ABORT) { + return ("Aborted"); + } + return ("UNKNOWN"); } -static bool _partitionIsBootable(const esp_partition_t* partition){ - uint8_t buf[4]; - if(!partition){ - return false; - } - if(!ESP.flashRead(partition->address, (uint32_t*)buf, 4)) { - return false; - } +static bool _partitionIsBootable(const esp_partition_t* partition) { + uint8_t buf[4]; + if (!partition) { + log_e("Invalid partition"); + return false; + } + if (!ESP.flashRead(partition->address, (uint32_t*)buf, 4)) { + log_e("Could not read first 4 bytes of the partition"); + return false; + } - if(buf[0] != ESP_IMAGE_HEADER_MAGIC) { - return false; - } - return true; + if (buf[0] != ESP_IMAGE_HEADER_MAGIC) { + log_e("Invalid magic (%02x %02x %02x %02x)", buf[0], buf[1], buf[2], buf[3]); + return false; + } + return true; } -static bool _enablePartition(const esp_partition_t* partition){ - uint8_t buf[4]; - if(!partition){ - return false; - } - if(!ESP.flashRead(partition->address, (uint32_t*)buf, 4)) { - return false; - } - buf[0] = ESP_IMAGE_HEADER_MAGIC; +static bool _enablePartition(const esp_partition_t* partition) { + uint8_t buf[4]; + if (!partition) { + log_e("Invalid partition"); + return false; + } + if (!ESP.flashRead(partition->address, (uint32_t*)buf, 4)) { + log_e("Could not read first 4 bytes of the partition"); + return false; + } + buf[0] = ESP_IMAGE_HEADER_MAGIC; - return ESP.flashWrite(partition->address, (uint32_t*)buf, 4); + if (!ESP.flashWrite(partition->address, (uint32_t*)buf, 4)) { + log_e("Could not repair the magic (%02x %02x %02x %02x)", buf[0], buf[1], buf[2], buf[3]); + return false; + } + return true; } -UpdateClass::UpdateClass() -: _error(0) -, _buffer(0) -, _bufferLen(0) -, _size(0) -, _progress_callback(NULL) -, _progress(0) -, _command(U_FLASH) -, _partition(NULL) + + +UpdateClass::UpdateClass(UpdateProcessor * processor) + : _processor(0) + , _error(0) + , _buffer(0) + , _bufferLen(0) + , _size(0) + , _progress_callback(NULL) + , _progress(0) + , _progress_flash(0) + , _command(U_FLASH) + , _partition(NULL) + , _post_header(false) { + // Old legacy behaviour is the default. + if (_processor == NULL) + _processor = new UpdateProcessorLegacy(); } +UpdateClass::~UpdateClass() { + _reset(); + if (_processor) + delete(_processor); +} + +void UpdateClass::setProcessor(UpdateProcessor * processor) { + _reset(); + if (_processor) + delete(_processor); + _processor = processor; +}; + UpdateClass& UpdateClass::onProgress(THandlerFunction_Progress fn) { - _progress_callback = fn; - return *this; + _progress_callback = fn; + return *this; } void UpdateClass::_reset() { - if (_buffer) - delete[] _buffer; - _buffer = 0; - _bufferLen = 0; - _progress = 0; - _size = 0; - _command = U_FLASH; - - if(_ledPin != -1) { - digitalWrite(_ledPin, !_ledOn); // off - } + if (_buffer) + delete[] _buffer; + _buffer = 0; + _bufferLen = 0; + _progress = 0; + _progress_flash = 0; + _size = 0; + _command = U_FLASH; + _post_header = false; + + if (_processor) + _processor->reset(); + + if (_ledPin != -1) { + digitalWrite(_ledPin, !_ledOn); // off + } } -bool UpdateClass::canRollBack(){ - if(_buffer){ //Update is running - return false; - } - const esp_partition_t* partition = esp_ota_get_next_update_partition(NULL); - return _partitionIsBootable(partition); +bool UpdateClass::canRollBack() { + if (_buffer) { //Update is running + return false; + } + const esp_partition_t* partition = esp_ota_get_next_update_partition(NULL); + return _partitionIsBootable(partition); } -bool UpdateClass::rollBack(){ - if(_buffer){ //Update is running - return false; - } - const esp_partition_t* partition = esp_ota_get_next_update_partition(NULL); - return _partitionIsBootable(partition) && !esp_ota_set_boot_partition(partition); +bool UpdateClass::rollBack() { + if (_buffer) { //Update is running + return false; + } + const esp_partition_t* partition = esp_ota_get_next_update_partition(NULL); + return _partitionIsBootable(partition) && !esp_ota_set_boot_partition(partition); } bool UpdateClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) { - if(_size > 0){ - log_w("already running"); - return false; - } + if (_size > 0) { + log_w("already running"); + return false; + } - _ledPin = ledPin; - _ledOn = !!ledOn; // 0(LOW) or 1(HIGH) + _ledPin = ledPin; + _ledOn = !!ledOn; // 0(LOW) or 1(HIGH) - _reset(); - _error = 0; + _reset(); + _error = 0; - if(size == 0) { - _error = UPDATE_ERROR_SIZE; - return false; - } + if (size == 0) { + _error = UPDATE_ERROR_SIZE; + return false; + } + + if (command == U_FLASH) { + _partition = esp_ota_get_next_update_partition(NULL); + if (!_partition) { + _error = UPDATE_ERROR_NO_PARTITION; + return false; + } + log_d("OTA Partition: %s", _partition->label); + } + else if (command == U_SPIFFS) { + _partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, NULL); + if (!_partition) { + _error = UPDATE_ERROR_NO_PARTITION; + return false; + } + } + else { + _error = UPDATE_ERROR_BAD_ARGUMENT; + log_e("bad command %u", command); + return false; + } - if (command == U_FLASH) { - _partition = esp_ota_get_next_update_partition(NULL); - if(!_partition){ - _error = UPDATE_ERROR_NO_PARTITION; - return false; - } - log_d("OTA Partition: %s", _partition->label); - } - else if (command == U_SPIFFS) { - _partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, NULL); - if(!_partition){ - _error = UPDATE_ERROR_NO_PARTITION; - return false; - } - } - else { - _error = UPDATE_ERROR_BAD_ARGUMENT; - log_e("bad command %u", command); - return false; - } + if (size == UPDATE_SIZE_UNKNOWN) { + size = _partition->size; + } else if (size > _partition->size) { + _error = UPDATE_ERROR_SIZE; + log_e("too large %u > %u", size, _partition->size); + return false; + } - if(size == UPDATE_SIZE_UNKNOWN){ - size = _partition->size; - } else if(size > _partition->size){ - _error = UPDATE_ERROR_SIZE; - log_e("too large %u > %u", size, _partition->size); - return false; - } + //initialize + assert(_buffer == NULL); - //initialize - _buffer = (uint8_t*)malloc(SPI_FLASH_SEC_SIZE); - if(!_buffer){ - log_e("malloc failed"); - return false; - } - _size = size; - _command = command; - _md5.begin(); - return true; + _buffer = (uint8_t*)malloc(SPI_FLASH_SEC_SIZE); + if (!_buffer) { + log_e("malloc failed"); + return false; + } + _size = size; + _command = command; + return true; } -void UpdateClass::_abort(uint8_t err){ - _reset(); - _error = err; +void UpdateClass::_abort(uint8_t err) { + _reset(); + _error = err; } -void UpdateClass::abort(){ - _abort(UPDATE_ERROR_ABORT); +void UpdateClass::abort() { + log_d("Aborted."); + _abort(UPDATE_ERROR_ABORT); } -bool UpdateClass::_writeBuffer(){ - //first bytes of new firmware - if(!_progress && _command == U_FLASH){ - //check magic - if(_buffer[0] != ESP_IMAGE_HEADER_MAGIC){ - _abort(UPDATE_ERROR_MAGIC_BYTE); - return false; - } - //remove magic byte from the firmware now and write it upon success - //this ensures that partially written firmware will not be bootable - _buffer[0] = 0xFF; - } - if (!_progress && _progress_callback) { - _progress_callback(0, _size); - } - if(!ESP.flashEraseSector((_partition->address + _progress)/SPI_FLASH_SEC_SIZE)){ - _abort(UPDATE_ERROR_ERASE); - return false; - } - if (!ESP.flashWrite(_partition->address + _progress, (uint32_t*)_buffer, _bufferLen)) { - _abort(UPDATE_ERROR_WRITE); - return false; - } - //restore magic or md5 will fail - if(!_progress && _command == U_FLASH){ - _buffer[0] = ESP_IMAGE_HEADER_MAGIC; - } - _md5.add(_buffer, _bufferLen); - _progress += _bufferLen; - _bufferLen = 0; - if (_progress_callback) { - _progress_callback(_progress, _size); +bool UpdateClass::_writeBuffer() { + UpdateProcessor::secure_update_processor_err_t e = UpdateProcessor::secure_update_processor_ERROR; + + if (_progress_callback) + _progress_callback(_progress, _size); + + if (_ledPin != -1) + digitalWrite(_ledPin, !digitalRead(_ledPin)); // Flash LED. + + if (!_post_header) { + size_t l = _bufferLen; + e = _processor->process_header(&_command, _buffer, &_bufferLen); + + if ((e == UpdateProcessor::secure_update_processor_AGAIN) && + (_bufferLen < SPI_FLASH_SEC_SIZE) && + (_bufferLen < _size) + ) { + _progress += (l - _bufferLen); + return true; } + + if (e != UpdateProcessor::secure_update_processor_OK) { + log_e("Error duing processing of process_header."); + _abort(UPDATE_ERROR_BAD_ARGUMENT); + return false; + }; + + _progress += (l - _bufferLen); + _post_header = true; return true; -} + }; -bool UpdateClass::_verifyHeader(uint8_t data) { - if(_command == U_FLASH) { - if(data != ESP_IMAGE_HEADER_MAGIC) { - _abort(UPDATE_ERROR_MAGIC_BYTE); - return false; - } - return true; - } else if(_command == U_SPIFFS) { - return true; - } + if (_bufferLen != SPI_FLASH_SEC_SIZE && remaining() != _bufferLen) { + log_e("Buffer passed is too short (and not the last one) - should never happen"); + _abort(UPDATE_ERROR_STREAM); return false; -} + } -bool UpdateClass::_verifyEnd() { - if(_command == U_FLASH) { - if(!_enablePartition(_partition) || !_partitionIsBootable(_partition)) { - _abort(UPDATE_ERROR_READ); - return false; - } - - if(esp_ota_set_boot_partition(_partition)){ - _abort(UPDATE_ERROR_ACTIVATE); - return false; - } - _reset(); - return true; - } else if(_command == U_SPIFFS) { - _reset(); - return true; - } - return false; -} + e = _processor->process_payload(_buffer, &_bufferLen); -bool UpdateClass::setMD5(const char * expected_md5){ - if(strlen(expected_md5) != 32) - { - return false; - } - _target_md5 = expected_md5; + if (e == UpdateProcessor::secure_update_processor_AGAIN) return true; + + if (e != UpdateProcessor::secure_update_processor_OK) { + log_e("Failed to process the payload."); + _abort(UPDATE_ERROR_STREAM); + return false; + } + + if (_progress_flash == 0) { + //remove magic byte from the firmware now and write it upon success + //this ensures that partially written firmware will not be bootable + assert(_buffer[0] == ESP_IMAGE_HEADER_MAGIC); + _buffer[0] = 0xFF; + } + if (!ESP.flashEraseSector((_partition->address + _progress_flash) / SPI_FLASH_SEC_SIZE)) { + _abort(UPDATE_ERROR_ERASE); + return false; + }; + + + if (!ESP.flashWrite(_partition->address + _progress_flash, (uint32_t*)_buffer, _bufferLen)) { + _abort(UPDATE_ERROR_WRITE); + return false; + } + + _progress_flash += _bufferLen; + _progress += _bufferLen; + _bufferLen = 0; + return true; } -bool UpdateClass::end(bool evenIfRemaining){ - if(hasError() || _size == 0){ - return false; - } - if(!isFinished() && !evenIfRemaining){ - log_e("premature end: res:%u, pos:%u/%u\n", getError(), progress(), _size); - _abort(UPDATE_ERROR_ABORT); - return false; +bool UpdateClass::activate() { + if (_command == U_FLASH) { + if (!_enablePartition(_partition) || !_partitionIsBootable(_partition)) { + log_e("could not enable/bootable partition"); + _abort(UPDATE_ERROR_READ); + return false; } - if(evenIfRemaining) { - if(_bufferLen > 0) { - _writeBuffer(); - } - _size = progress(); + if (esp_ota_set_boot_partition(_partition)) { + log_e("could not set the boot partition"); + _abort(UPDATE_ERROR_ACTIVATE); + return false; } + _reset(); + + if (1) { + unsigned char buff[MBEDTLS_MD_MAX_SIZE]; + mbedtls_md_context_t ctx; + String out = ""; + const mbedtls_md_info_t * t = mbedtls_md_info_from_string("SHA256"); + + mbedtls_md_init(&ctx); + assert(0 == mbedtls_md_setup(&ctx, t, 0)); + + for (size_t l = 0; l < _progress_flash;) { + unsigned char buf[1024]; + size_t len = _progress_flash - l; + if (len > sizeof(buff)) len = sizeof(buff); + + ESP.flashRead(_partition->address + l, (uint32_t*)buf, len); + assert(0 == mbedtls_md_update(&ctx, buff, len)); + + l += len; + }; + + assert(0 == mbedtls_md_finish(&ctx, buff)); - _md5.calculate(); - if(_target_md5.length()) { - if(_target_md5 != _md5.toString()){ - _abort(UPDATE_ERROR_MD5); - return false; - } + for (int i = 0; i < mbedtls_md_get_size(t); i++) + out += String(buff[i], HEX); + + log_d("RAW %s Digest %s", mbedtls_md_get_name(t), out.c_str()); + mbedtls_md_free(&ctx); } - return _verifyEnd(); + return true; + } else if (_command == U_SPIFFS) { + _reset(); + return true; + } + return false; } -size_t UpdateClass::write(uint8_t *data, size_t len) { - if(hasError() || !isRunning()){ - return 0; - } +bool UpdateClass::end(bool evenIfRemaining) { - if(len > remaining()){ - _abort(UPDATE_ERROR_SPACE); - return 0; - } + if (hasError() || _size == 0) { + log_e("Has error"); + _reset(); + return false; + } - size_t left = len; + if (!isFinished() && !evenIfRemaining) { + log_e("premature end: res:%u, pos:%u/%u\n", getError(), progress(), _size); + _abort(UPDATE_ERROR_ABORT); + _reset(); + return false; + } - while((_bufferLen + left) > SPI_FLASH_SEC_SIZE) { - size_t toBuff = SPI_FLASH_SEC_SIZE - _bufferLen; - memcpy(_buffer + _bufferLen, data + (len - left), toBuff); - _bufferLen += toBuff; - if(!_writeBuffer()){ - return len - left; - } - left -= toBuff; + if (evenIfRemaining) { + if (_bufferLen > 0) { + _writeBuffer(); } - memcpy(_buffer + _bufferLen, data + (len - left), left); - _bufferLen += left; - if(_bufferLen == remaining()){ - if(!_writeBuffer()){ - return len - left; - } - } - return len; + _size = progress(); + } + + if (_processor->process_end() != UpdateProcessor::secure_update_processor_OK) { + log_e("Error during finalizing."); + _abort(UPDATE_ERROR_ABORT); + _reset(); + return false; + }; + + log_d("Reporting an OK back up the chain"); + return true; } -size_t UpdateClass::writeStream(Stream &data) { - size_t written = 0; - size_t toRead = 0; - if(hasError() || !isRunning()) - return 0; +size_t UpdateClass::write(uint8_t *data, size_t len) { + size_t written = 0; + + if (hasError() || !isRunning()) { + log_e("Error or not running"); + return 0; + } + + if (len > remaining()) { + log_e("Overfill (%d in buffer, %d left to go), did %d, would do %d, expecting %d", len, remaining(), _progress, _progress + len, size()); + _abort(UPDATE_ERROR_SPACE); + return 0; + } + + while (written < len) { + size_t l = len - written; + if (l + _bufferLen > SPI_FLASH_SEC_SIZE) + l = SPI_FLASH_SEC_SIZE - _bufferLen; + + memcpy(_buffer + _bufferLen, data + written, l); + _bufferLen += l; + written += l; + + if (_bufferLen == SPI_FLASH_SEC_SIZE || _bufferLen == remaining()) { + if (!_writeBuffer()) { + log_d("Writebuffer fail after %d bytes written.", written); + return written; + }; + }; + } + return written; +} - if(!_verifyHeader(data.peek())) { - _reset(); - return 0; - } +template +size_t UpdateClass::write(T & data) { + size_t written = 0; + if (hasError() || !isRunning()) + return 0; + + size_t available = data.available(); + while (available) { + if (_bufferLen + available > remaining()) { + available = remaining() - _bufferLen; + } + + size_t toBuff = _bufferLen + available; + if (toBuff > SPI_FLASH_SEC_SIZE) + toBuff = SPI_FLASH_SEC_SIZE; + + data.read(_buffer + _bufferLen, toBuff); + _bufferLen += toBuff; + + if (_bufferLen == SPI_FLASH_SEC_SIZE || remaining() == _bufferLen) { + log_d("_writeBuffer(%d), remain %d, SEC %d", _bufferLen, remaining(), SPI_FLASH_SEC_SIZE); + if (!_writeBuffer()) { + return written; + }; + } else { + log_d("_writeBuffer(skip)"); + }; + + written += toBuff; + if (remaining() == 0) + return written; + + available = data.available(); + } + return written; +} - if(_ledPin != -1) { - pinMode(_ledPin, OUTPUT); - } - while(remaining()) { - if(_ledPin != -1) { - digitalWrite(_ledPin, _ledOn); // Switch LED on - } - size_t bytesToRead = SPI_FLASH_SEC_SIZE - _bufferLen; - if(bytesToRead > remaining()) { - bytesToRead = remaining(); - } - - toRead = data.readBytes(_buffer + _bufferLen, bytesToRead); - if(toRead == 0) { //Timeout - delay(100); - toRead = data.readBytes(_buffer + _bufferLen, bytesToRead); - if(toRead == 0) { //Timeout - _abort(UPDATE_ERROR_STREAM); - return written; - } - } - if(_ledPin != -1) { - digitalWrite(_ledPin, !_ledOn); // Switch LED off - } - _bufferLen += toRead; - if((_bufferLen == remaining() || _bufferLen == SPI_FLASH_SEC_SIZE) && !_writeBuffer()) - return written; - written += toRead; - } - return written; +size_t UpdateClass::writeStream(Stream & data) { + size_t written = 0; + size_t toRead = 0; + if (hasError() || !isRunning()) + return 0; + + while (remaining()) { + size_t bytesToRead = SPI_FLASH_SEC_SIZE - _bufferLen; + if (bytesToRead > remaining()) { + bytesToRead = remaining(); + } + + toRead = data.readBytes(_buffer + _bufferLen, bytesToRead); + if (toRead == 0) { //Timeout + delay(100); + toRead = data.readBytes(_buffer + _bufferLen, bytesToRead); + if (toRead == 0) { //Timeout + _abort(UPDATE_ERROR_STREAM); + return written; + } + } + _bufferLen += toRead; + if (_bufferLen == remaining() || _bufferLen == SPI_FLASH_SEC_SIZE) { + log_d("_writeBuffer(%d), remain %d, SEC %d", _bufferLen, remaining(), SPI_FLASH_SEC_SIZE); + if (!_writeBuffer()) + return written; + } else { + log_d("_writeBuffer(skip)"); + }; + written += toRead; + } + return written; } -void UpdateClass::printError(Stream &out){ - out.println(_err2str(_error)); +void UpdateClass::printError(Stream & out) { + out.println(_err2str(_error)); } -const char * UpdateClass::errorString(){ - return _err2str(_error); +const char * UpdateClass::errorString() { + return _err2str(_error); } UpdateClass Update; diff --git a/libraries/Update/src/mbedtls-ts-addons/sign.h b/libraries/Update/src/mbedtls-ts-addons/sign.h new file mode 100644 index 00000000000..465b8c4e4bc --- /dev/null +++ b/libraries/Update/src/mbedtls-ts-addons/sign.h @@ -0,0 +1,85 @@ +#include "mbedtls/config.h" +#include "mbedtls/error.h" +#include "mbedtls/asn1.h" +#include "mbedtls/error.h" +#include "mbedtls/oid.h" +#include "mbedtls/config.h" +#include "mbedtls/error.h" +#include "mbedtls/asn1.h" +#include "mbedtls/error.h" +#include "mbedtls/oid.h" +#include "mbedtls/x509.h" +#include "mbedtls/x509_crt.h" +#include +#include +#include + +#ifndef _H_SIGN_INFO +#define _H_SIGN_INFO + +/* As used for S/MIME, RFC3161 and various others + + See: RFC 5652 - Cryptographic Message Syntax (CMS) + + SignerInfo ::= SEQUENCE { + version CMSVersion, + sid SignerIdentifier, + digestAlgorithm DigestAlgorithmIdentifier, + signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL, + signatureAlgorithm SignatureAlgorithmIdentifier, + signature SignatureValue, + unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL } + + SignerIdentifier ::= CHOICE { + issuerAndSerialNumber IssuerAndSerialNumber, + subjectKeyIdentifier [0] SubjectKeyIdentifier } + + SignedAttributes ::= SET SIZE (1..MAX) OF Attribute + + UnsignedAttributes ::= SET SIZE (1..MAX) OF Attribute + + Attribute ::= SEQUENCE { + attrType OBJECT IDENTIFIER, + attrValues SET OF AttributeValue } + + IssuerAndSerialNumber ::= SEQUENCE { + issuer Name, + serialNumber CertificateSerialNumber } + + AttributeValue ::= ANY + + SignatureValue ::= OCTET STRING +*/ + +typedef struct mbedtls_asn1_ts_pki_signer_info { + + // information on who placed the signature + // + mbedtls_x509_name sid_name; + mbedtls_asn1_bitstring sid_serial; + + // signature digest that is signed + //. + mbedtls_md_type_t sig_digest_type; + unsigned char * sig_digest; + size_t sig_digest_len; + + // signature that was placed + // + mbedtls_pk_type_t sig_type; + unsigned char * sig; + size_t sig_len; + + // area that was signed + // + unsigned char * signed_attribute_raw; + size_t signed_attribute_len; + + size_t signing_cert_hash_len = 0; + mbedtls_md_type_t signing_cert_hash_type; + unsigned char * signing_cert_hash; + +} mbedtls_asn1_ts_pki_signer_info; + +extern int mbedtls_ts_get_signer_info(unsigned char **p, unsigned char * end, mbedtls_asn1_ts_pki_signer_info * signer_info); +#endif diff --git a/libraries/Update/src/mbedtls-ts-addons/signer_info.cpp b/libraries/Update/src/mbedtls-ts-addons/signer_info.cpp new file mode 100644 index 00000000000..0b7b191d88a --- /dev/null +++ b/libraries/Update/src/mbedtls-ts-addons/signer_info.cpp @@ -0,0 +1,231 @@ +#include +#include +#include +#include + +#include "x509_ts_utils.h" +#include "ts.h" + + +#ifdef _TS_DEBUG +#define CHKR(r) { if ( ( ret = (r) ) != 0 ) { assert(0); return ret; }; } +#else +#define CHKR(r) { if ( ( ret = (r) ) != 0 ) { return ret; }; } +#endif +/* + SignerInfo ::= SEQUENCE + version CMSVersion, + sid SignerIdentifier, + digestAlgorithm DigestAlgorithmIdentifier, + signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL, + signatureAlgorithm SignatureAlgorithmIdentifier, + signature SignatureValue, + unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL + + SignerIdentifier ::= CHOICE + issuerAndSerialNumber IssuerAndSerialNumber, + subjectKeyIdentifier [0] SubjectKeyIdentifier + + SignedAttributes ::= SET SIZE (1..MAX) OF Attribute + + UnsignedAttributes ::= SET SIZE (1..MAX) OF Attribute + + Attribute ::= SEQUENCE + attrType OBJECT IDENTIFIER, + attrValues SET OF AttributeValue + + IssuerAndSerialNumber ::= SEQUENCE + issuer Name, + serialNumber CertificateSerialNumber + + AttributeValue ::= ANY + + SignatureValue ::= OCTET STRING +*/ +int mbedtls_ts_get_signer_info(unsigned char **p, unsigned char * end, mbedtls_asn1_ts_pki_signer_info * signer_info) +{ + unsigned char *sap = NULL, *eap = NULL; + int sigVersion; + size_t len; + int ret = MBEDTLS_ERR_X509_INVALID_FORMAT; + + + mbedtls_asn1_ts_pki_signer_info out; + bzero(&out, sizeof(out)); + + CHKR(mbedtls_asn1_get_tag( p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) ); + + eap = *p + len; + + CHKR(mbedtls_asn1_get_int( p, end, &sigVersion)); + + + switch (sigVersion) { + case 1: + CHKR(mbedtls_asn1_get_tag( p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) ); + CHKR( mbedtls_asn1_get_tag( p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) ); + CHKR(mbedtls_x509_get_name( p, *p + len, &(out.sid_name))); + +#ifdef _TS_DEBUG + { + char s[1024]; + mbedtls_x509_dn_gets(s, sizeof(s), &(out.sid_name)); + _TS_DEBUG_PRINTF("SID name %s\n", s); + }; +#endif + + CHKR(mbedtls_asn1_get_serial_bitstring( p, end, &(out.sid_serial))); + break; + case 3: + // return unsuppoted + assert(1); // todo ! + break; + default: + assert(1); + return ( MBEDTLS_ERR_TS_REPLY_INVALID_FORMAT); + break; + } + + _TS_DEBUG_PRINTF("DigestAlgorithmIdentifier:"); + + CHKR(mbedtls_asn1_get_algorithm_itentifiers(p, *p + len, (int*) & (out.sig_digest_type))); + + out.signing_cert_hash_type = MBEDTLS_MD_SHA1; // Default for for V1 - ss rfc 5035, section 5.4 + sap = *p; + if (mbedtls_asn1_get_tag( p, end, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_CONTEXT_SPECIFIC) == 0) { + + // i.e. the block that is signed; + out.signed_attribute_len = len + *p - sap; + out.signed_attribute_raw = sap; + + unsigned char *ep = *p + len; + _TS_DEBUG_PRINTF("Signed attributes:\n"); + while (*p < ep) { + /* Attribute ::= SEQUENCE { + attrType OBJECT IDENTIFIER, + attrValues SET OF AttributeValue + AttributeValue ::= ANY + } + */ + CHKR(mbedtls_asn1_get_tag( p, ep, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)); + + unsigned char *eap = *p + len; + + CHKR(mbedtls_asn1_get_tag(p, ep, &len, MBEDTLS_ASN1_OID) ); + + _TS_DEBUG_PRINTF(" %s:\n", _oidbuff2str(*p, len)); + + mbedtls_x509_buf oid; + oid.p = *p; + oid.len = len; + *p += len; + + CHKR(mbedtls_asn1_get_tag(p, eap, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SET) ); + + unsigned char *evap = *p + len; + + // Is it the digest - as that is the one we need. + if ( MBEDTLS_OID_CMP( MBEDTLS_OID_PKCS9_MESSAGE_DIGEST, &oid ) == 0 ) { + CHKR(out.sig_digest != NULL); + CHKR(mbedtls_asn1_get_tag(p, evap, &len, MBEDTLS_ASN1_OCTET_STRING) ); + +#ifdef _TS_DEBUG + _TS_DEBUG_PRINTF(" Digest (signed section): len=%d: ", len); + for (int i = 0; i < len; i++) _TS_DEBUG_PRINTF("%02x", (*p)[i]); + _TS_DEBUG_PRINTF(".\n"); +#endif + // Only pick it if it is longer. + if (len > out.sig_digest_len) { + out.sig_digest = *p; + out.sig_digest_len = len; + }; + } + else if ( MBEDTLS_OID_CMP( MBEDTLS_OID_PKCS9_SMIME_AA_SIGNING_CERT, &oid ) == 0 ) { + CHKR(mbedtls_asn1_get_tag(p, evap, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) ); + CHKR(mbedtls_asn1_get_tag(p, evap, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) ); + CHKR(mbedtls_asn1_get_tag(p, evap, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) ); + CHKR(mbedtls_asn1_get_tag(p, evap, &len, MBEDTLS_ASN1_OCTET_STRING) ); + + // Only pick it if it is better. + if (len > out.signing_cert_hash_len) { + out.signing_cert_hash = *p; + out.signing_cert_hash_len = len; + }; +#ifdef _TS_DEBUG + _TS_DEBUG_PRINTF(" Signing cert hash V1 (%d): ", out.signing_cert_hash_type); + for (int i = 0; i < len; i++) _TS_DEBUG_PRINTF("%02x", out.signing_cert_hash[i]); + _TS_DEBUG_PRINTF(".\n"); +#endif + } + else if ( MBEDTLS_OID_CMP( MBEDTLS_OID_PKCS9_SMIME_AA_SIGNING_CERT_V2, &oid ) == 0 ) { + /* SigningCertificateV2 ::= SEQUENCE + certs SEQUENCE OF ESSCertIDv2 + policies SEQUENCE OF PolicyInformation OPTIONAL + + ESSCertIDv2 ::= SEQUENCE + hashAlgorithm AlgorithmIdentifier DEFAULT {algorithm id-sha256}, + certHash Hash, + issuerSerial IssuerSerial OPTIONAL + + Hash ::= OCTET STRING IssuerSerial ::= SEQUENCE + issuer GeneralNames, + serialNumber CertificateSerialNumber + */ + CHKR(mbedtls_asn1_get_tag(p, evap, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) ); + + mbedtls_md_type_t md_alg; + CHKR(mbedtls_asn1_get_algorithm_itentifiers(p, evap, (int *)&md_alg)); + + out.signing_cert_hash_type = md_alg; + + // Sort of pray for a general name that is a normal hash + CHKR(mbedtls_asn1_get_tag(p, evap, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) ); + CHKR(mbedtls_asn1_get_tag(p, evap, &len, MBEDTLS_ASN1_OCTET_STRING) ); + + out.signing_cert_hash = *p; + out.signing_cert_hash_len = len; + _TS_DEBUG_PRINTF(" Signing cert hash (%d) V2: ", out.signing_cert_hash_type); + for (int i = 0; i < len; i++) _TS_DEBUG_PRINTF("%02x", out.signing_cert_hash[i]); + _TS_DEBUG_PRINTF(".\n"); + } + else { + _TS_DEBUG_PRINTF(" Not decoded -- skipped\n"); + } + // skip over all else. + *p = evap; + }; // while loop over signed attributes + }; // if signed attributes + + _TS_DEBUG_PRINTF("SignatureAlgorithmIdentifier:"); + CHKR(mbedtls_asn1_get_algorithm_itentifiers(p, *p + len, (int *) & (out.sig_type))); + + /* + signatureAlgorithm SignatureAlgorithmIdentifier, + signature SignatureValue, <---- + unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL + */ + CHKR(mbedtls_asn1_get_tag(p, end, &len, MBEDTLS_ASN1_OCTET_STRING) ); + + out.sig = *p; + out.sig_len = len; + +#ifdef _TS_DEBUG + _TS_DEBUG_PRINTF("Signature (%d bytes): ", out.sig_len); + for (int i = 0; i < out.sig_len; i++) { + _TS_DEBUG_PRINTF("%02x", out.sig[i]); + } + _TS_DEBUG_PRINTF(".\n"); +#endif + + + /* unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL + */ + *p = eap; // skip. + + *signer_info = out; + + return 0; +} diff --git a/libraries/Update/src/mbedtls-ts-addons/ts.cpp b/libraries/Update/src/mbedtls-ts-addons/ts.cpp new file mode 100644 index 00000000000..d2ba505f2bc --- /dev/null +++ b/libraries/Update/src/mbedtls-ts-addons/ts.cpp @@ -0,0 +1,739 @@ + +#include +#if defined(MBEDTLS_PLATFORM_C) +#include "mbedtls/platform.h" +#else +#include +#include +#define mbedtls_free free +#define mbedtls_calloc calloc +#define mbedtls_printf _TS_DEBUG_PRINTF +#define mbedtls_snprintf _TS_DEBUG_SNPRINTF +#endif + +#include "ts.h" +#include "sign.h" +#include + +#ifdef _TS_DEBUG +#define CHKR(r) { if ( ( ret = (r) ) != 0 ) { assert(0); return ret; }; } +#define CHKRE(r,e) { if ( (r) != 0 ) { assert(0); return e; }; } +#define CHK(r) { if ( ( ret = (r) ) != 0 ) { _TS_DEBUG_PRINTF("At @%d in rfc\n", p-from); assert(0); goto ts_reply_free_and_exit; }; } +#define CHKE(r,e) { if ( ( (r) ) != 0 ) { _TS_DEBUG_PRINTF("At @%d in rfc\n", p-from); assert(0); ret = (e); goto ts_reply_free_and_exit; }; } +#else +#define CHKR(r) { if ( ( ret = (r) ) != 0 ) { return ret; }; } +#define CHKRE(r,e) { if ( (r) != 0 ) { return (e); }; } +#define CHK(r) { if ( ( ret = (r) ) != 0 ) { goto ts_reply_free_and_exit; }; } +#define CHKE(r,e) { if ( ( (r) ) != 0 ) { ret = (e); goto ts_reply_free_and_exit; }; } +#endif + +/* + PKIStatusInfo ::= SEQUENCE { + status PKIStatus, + statusString PKIFreeText OPTIONAL, + failInfo PKIFailureInfo OPTIONAL } + + PKIFailureInfo ::= BIT STRING { + badAlg (0), + -- unrecognized or unsupported Algorithm Identifier + badRequest (2), + -- transaction not permitted or supported + badDataFormat (5), + -- the data submitted has the wrong format + timeNotAvailable (14), + -- the TSA's time source is not available + unacceptedPolicy (15), + -- the requested TSA policy is not supported by the TSA + unacceptedExtension (16), + -- the requested extension is not supported by the TSA + addInfoNotAvailable (17) + -- the additional information requested could not be understood + -- or is not available + systemFailure (25) + -- the request cannot be handled due to system failure + } +*/ +int mbedtls_asn1_get_ts_pkistatusinfo(unsigned char **p, unsigned char * end, mbedtls_asn1_ts_pki_status_info * status) +{ + mbedtls_asn1_ts_pki_status_info out; + size_t len; + char statusBuff[2 * 1024]; + int ret = MBEDTLS_ERR_X509_INVALID_FORMAT; + + out.statusString = NULL; + out.failInfo = -1; + out.pki_status = -1; + + CHKR(mbedtls_asn1_get_tag( p, end, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE )); + + unsigned char *endp = *p + len; + + CHKRE((len >= (size_t) (end - *p )), MBEDTLS_ERR_X509_INVALID_FORMAT); + + CHKR(mbedtls_asn1_get_int( p, end, &(out.pki_status))); + + _TS_DEBUG_PRINTF("PKIStatus: %d\n", out.pki_status); + + if (*p < endp) { + if (mbedtls_asn1_get_tag(p, *p + len, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE ) == 0) { + + // we have a sequence of PKIFreeText + // PKIFreeText ::= SEQUENCE SIZE (1..MAX) OF UTF8String + // which we'll concatenate in one buffer, with '\n' in between. + // + unsigned char * endp = *p + len; + while (*p < endp) { + CHKR(mbedtls_asn1_get_tag( p, end, &len, MBEDTLS_ASN1_UTF8_STRING )); + + int at = 0, l = len; + if (statusBuff[0]) { + at = strlen(statusBuff); + statusBuff[at] = '\n'; + at++; + }; + if (at + l > sizeof(statusBuff) + 1) { + l = sizeof(statusBuff) - at - 1; + } + if (l > 0) { + memcpy(statusBuff + at, *p, l); + statusBuff[l] = '\0'; + }; + *p += len; + }; + _TS_DEBUG_PRINTF("PKIFreeText: %s\n", statusBuff); + } + + mbedtls_x509_bitstring bs; + if ( mbedtls_asn1_get_bitstring(p, *p + len, &bs ) == 0 ) { + _TS_DEBUG_PRINTF("PKIFailureInfo: %s\n", _bitstr(&bs)); + }; + + CHKRE((bs.len > 1), MBEDTLS_ERR_X509_INVALID_FORMAT); + + if (bs.len == 0) { + out.failInfo = 0; + } else { + unsigned char mask = 0xFF; + mask = mask >> bs.unused_bits; + out.failInfo = bs.p[0] & mask; + }; + }; + + /* check that we've read the whole PKIStatusInfo */ + CHKRE((*p != endp), MBEDTLS_ERR_X509_INVALID_FORMAT); + + if (statusBuff[0]) + out.statusString = strdup(statusBuff); + + *status = out; + return 0; +}; + + +/* + TSTInfo ::= SEQUENCE { + version INTEGER { v1(1) }, <--- + policy TSAPolicyId, + messageImprint MessageImprint, + -- MUST have the same value as the similar field in + -- TimeStampReq + serialNumber INTEGER, + -- Time-Stamping users MUST be ready to accommodate integers + -- up to 160 bits. + genTime GeneralizedTime, + accuracy Accuracy OPTIONAL, + ordering BOOLEAN DEFAULT FALSE, + nonce INTEGER OPTIONAL, + -- MUST be present if the similar field was present + -- in TimeStampReq. In that case it MUST have the same value. + tsa [0] GeneralName OPTIONAL, + extensions [1] IMPLICIT Extensions OPTIONAL } +*/ +int mbedtls_ts_get_tsinfo(unsigned char **p, unsigned char * end, mbedtls_asn1_ts_pki_ts_info * ts_info) +{ + mbedtls_asn1_ts_pki_ts_info out; + unsigned char * end_tsinfo = NULL; + size_t len; + int ret = MBEDTLS_ERR_TS_REPLY_INVALID_FORMAT; + + bzero(&out, sizeof(out)); + + CHKR(mbedtls_asn1_get_tag( p, end, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) ); + + end_tsinfo = *p + len; + + // version INTEGER { v1(1) }, <--- + int v = -1; + if ( ( ret = mbedtls_asn1_get_int( p, end_tsinfo, &v)) != 0) + return ret; + + CHKRE((v != 1), MBEDTLS_ERR_X509_INVALID_FORMAT); + + _TS_DEBUG_PRINTF("TSTInfo Version %d\n", v); + + // policy TSAPolicyId, + CHKR(mbedtls_asn1_get_tag(p, end_tsinfo, &len, MBEDTLS_ASN1_OID) ); + + // ignoring the policy ID. + *p += len; + + // messageImprint MessageImprint, + // MessageImprint ::= SEQUENCE + CHKR(mbedtls_asn1_get_tag( p, end_tsinfo, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) ); + // digestType + CHKR(mbedtls_asn1_get_algorithm_itentifiers( p, end_tsinfo, (int *)&out.digest_type) ); + // hashedMessage OCTET STRING } + CHKR(mbedtls_asn1_get_tag( p, end_tsinfo, &len, MBEDTLS_ASN1_OCTET_STRING) ); + + out.payload_digest = *p; + out.payload_digest_len = len; + *p += len; + +#ifdef _TS_DEBUG + _TS_DEBUG_PRINTF("The main hash of the payload: len=%d: ", out.payload_digest_len); + for (int i = 0; i < out.payload_digest_len; i++) _TS_DEBUG_PRINTF("%0x", out.payload_digest[i]); + _TS_DEBUG_PRINTF(".\n"); +#endif + + // serialNumber INTEGER, + { + uint64_t serial = 0; + CHKR(mbedtls_asn1_get_uint64(p, end_tsinfo, &serial)); + + _TS_DEBUG_PRINTF("Serial %llu\n", serial); + }; + /* + genTime GeneralizedTime, + accuracy Accuracy OPTIONAL, + ordering BOOLEAN DEFAULT FALSE, + */ + + CHKR(mbedtls_asn1_get_tag(p, end_tsinfo, &len, MBEDTLS_ASN1_GENERALIZED_TIME) ); + + CHKR(x509_parse_time( p, len, 4, &(out.signed_time))); + + _TS_DEBUG_PRINTF("Signature timestap: %04d-%02d-%02d %02d:%02d:%02d UTC\n", + out.signed_time.year, out.signed_time.mon, out.signed_time.day, out.signed_time.hour, out.signed_time.min, out.signed_time.sec); + + if (mbedtls_asn1_get_tag( p, end_tsinfo, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) == 0) { + unsigned char *ep = *p + len; + + // we have accuracy of seconds, millis, micros + int sec = 0, milli = 0, micro = 0; + int tryit = mbedtls_asn1_get_int( p, ep, &sec); + if (tryit == 0) tryit = mbedtls_asn1_get_int( p, ep, &milli); + if (tryit == 0) tryit = mbedtls_asn1_get_int( p, ep, µ); + + _TS_DEBUG_PRINTF("Accuracy %d.%d.%d (ignored %d bytes)\n", sec, milli, micro, ep - *p); + + // skip over any extra cruft (Observed at the BSI.de) + *p = ep; + } + + { + // ordering BOOLEAN DEFAULT FALSE, + int ordering = 0; + if (mbedtls_asn1_get_bool( p, end_tsinfo, &ordering) == 0) { + _TS_DEBUG_PRINTF("Ordering: %d\n", ordering); + } + } + // nonce INTEGER OPTIONAL, + { + uint64_t nonce = 0; + if (mbedtls_asn1_get_uint64( p, end_tsinfo, &nonce) == 0) { + _TS_DEBUG_PRINTF("Nonce!"); + }; + }; + + /* + tsa [0] GeneralName OPTIONAL, + GeneralName ::= CHOICE { + otherName [0] AnotherName, + rfc822Name [1] IA5String, + dNSName [2] IA5String, + x400Address [3] ORAddress, + directoryName [4] Name, + ediPartyName [5] EDIPartyName, + uniformResourceIdentifier [6] IA5String, + iPAddress [7] OCTET STRING, + registeredID [8] OBJECT IDENTIFIER } + */ + if ((((*p)[0]) & 0xF) == 0) { + // tsa [0] GeneralName OPTIONAL, + if ( mbedtls_asn1_get_tag( p, end_tsinfo, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_CONTEXT_SPECIFIC) == 0) { + mbedtls_x509_name directoryName; + int c = (*p)[0]; + CHKR(mbedtls_asn1_get_tag( p, end_tsinfo, &len, c )); + switch (c & 0x0F) { + case 4: // directoryName - Name + if ((ret = mbedtls_x509_get_names(p, *p + len, &directoryName)) != 0) + return ret; + char s[1024]; + mbedtls_x509_dn_gets(s, sizeof(s), &directoryName); + _TS_DEBUG_PRINTF("directoryName: %s\n", s); + break; + default: + assert(0); + break; + }; + }; + }; + + // extensions [1] IMPLICIT Extensions OPTIONAL + if ((((*p)[0]) & 0xF) == 1) + if (mbedtls_asn1_get_tag(p, end_tsinfo, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_CONTEXT_SPECIFIC) == 0) { + _TS_DEBUG_PRINTF("Has extensions -- len %d (Unparsed %d)\n", len, end_tsinfo - *p); + p += len; + }; + + assert(end_tsinfo == *p); + + // skip over anything else. + *p = end_tsinfo; + *ts_info = out; + + return 0; +} + +int mbedtls_ts_pki_status_info_free(mbedtls_asn1_ts_pki_status_info * status) { + if (status && status->statusString) + free(status->statusString); + return 0; +} +int mbedtls_asn1_ts_pki_ts_info_free(mbedtls_asn1_ts_pki_ts_info * tsinfo) { + return 0; +} + +#if 0 +int mbedtls_asn1_ts_pki_time_stamp_token_free(mbedtls_asn1_ts_pki_time_stamp_token * tstoken) { + if (tstoken && tstoken->chain) + free(tstoken->chain); + return 0; +} +#endif + +int mbedtls_ts_reply_free(struct mbedtls_ts_reply * reply) { + if (reply) { + mbedtls_ts_pki_status_info_free(&(reply->status)); + mbedtls_asn1_ts_pki_ts_info_free(&(reply->ts_info)); + // mbedtls_asn1_ts_pki_time_stamp_token_free(&(reply->timestamptoken)); + if (reply->chain) + free(reply->chain); + } + return 0; +}; + +int mbedtls_x509_ts_reply_parse_der(const unsigned char **buf, size_t * buflenp, mbedtls_ts_reply * reply) +{ + mbedtls_x509_crt ** chain = &(reply->chain), * crt = NULL; + unsigned char *p = NULL, *end = NULL, * from; + int ret = MBEDTLS_ERR_TS_CORRUPTION_DETECTED; + size_t len; + + // for the local verification. + size_t signed_data_digest_len = 0; + unsigned char signed_data_digest[MBEDTLS_MD_MAX_SIZE]; + mbedtls_md_type_t signed_data_digest_type = MBEDTLS_MD_NONE; + + const mbedtls_md_info_t * md_info = NULL; + + unsigned char * signed_data = NULL; + size_t signed_data_len = 0; + mbedtls_md_context_t md; + + if ( reply == NULL || buf == NULL || buflenp == NULL) { + return ( MBEDTLS_ERR_TS_BAD_INPUT_DATA ); // reuse + }; + p = (unsigned char *)*buf; + from = p; + end = p + *buflenp; + + /* TimeStampResp ::= SEQUENCE + status PKIStatusInfo, + timeStampToken TimeStampToken OPTIONAL + */ + CHK( mbedtls_asn1_get_tag( &p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE )); + + end = p + len; + + CHK(mbedtls_asn1_get_ts_pkistatusinfo( &p, end, &(reply->status))); + + /* + TimeStampResp ::= SEQUENCE { + status PKIStatusInfo, + timeStampToken TimeStampToken OPTIONAL <----- + + TimeStampToken ::= ContentInfo (a sequence) + -- contentType is id-signedData as defined in [CMS] + -- content is SignedData as defined in([CMS]) + -- eContentType within SignedData is id-ct-TSTInfo + -- eContent within SignedData is TSTInfo + */ + CHK(mbedtls_asn1_get_tag( &p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE )); + + + // this should be the whole file - reject trailing cruft. + if ( len != (size_t) ( end - p ) ) { + ret = ( MBEDTLS_ERR_X509_INVALID_FORMAT ); + goto ts_reply_len_fault_and_exit; + }; + + CHK(mbedtls_asn1_get_tag(&p, end, &len, MBEDTLS_ASN1_OID)); + + mbedtls_x509_buf idSig; + idSig.p = p; + idSig.len = len; + p += len; + + _TS_DEBUG_PRINTF("idSig: %3d %s\n", len, _oid2str(&idSig)); + + if ( MBEDTLS_OID_CMP( MBEDTLS_OID_PKCS7_ID_SIGNED, &idSig ) != 0 ) { + ret = ( MBEDTLS_ERR_X509_INVALID_FORMAT ); + goto ts_reply_free_and_exit; + } + /* + TimeStampToken ::= ContentInfo (a sequence) + -- contentType is id-signedData as defined in [CMS] + SignedData ::= SEQUENCE <---- + version CMSVersion, + digestAlgorithms DigestAlgorithmIdentifiers, + encapContentInfo EncapsulatedContentInfo, + certificates [0] IMPLICIT CertificateSet OPTIONAL, + crls [1] IMPLICIT RevocationInfoChoices OPTIONAL, + signerInfos SignerInfos + + */ + CHK(mbedtls_asn1_get_tag( &p, end, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_CONTEXT_SPECIFIC) ); + + CHK(mbedtls_asn1_get_tag( &p, end, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE ) ); + + CHKE(( len > (size_t) ( end - p )), MBEDTLS_ERR_X509_INVALID_FORMAT); + + // version CMSVersion, + { + int v; + CHK(mbedtls_asn1_get_int( &p, end, &v)); + _TS_DEBUG_PRINTF("SignedData Version %d\n", v); + }; + + CHK(mbedtls_asn1_get_tag( &p, end, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SET) ); + + // DigestAlgorithmIdentifier(s) + _TS_DEBUG_PRINTF("SignedData DigestAlgorithmIdentifier:"); + + CHK(mbedtls_asn1_get_algorithm_itentifiers( &p, p + len, (int *)&signed_data_digest_type) ); + + _TS_DEBUG_PRINTF("SignedData DigestAlgorithmIdentifier: %d", signed_data_digest_type); + + CHK(mbedtls_asn1_get_tag( &p, end, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE ) ); + + CHK(mbedtls_asn1_get_tag(&p, end, &len, MBEDTLS_ASN1_OID) ); + + mbedtls_x509_buf eContentType; + eContentType.p = p; + eContentType.len = len; + p += len; + _TS_DEBUG_PRINTF("eContentType: %3d %s\n", len, _oid2str(&eContentType)); + + CHKE((MBEDTLS_OID_CMP( MBEDTLS_OID_STINFO, &eContentType ) != 0 ), MBEDTLS_ERR_X509_INVALID_FORMAT); + + /* + EncapsulatedContentInfo ::= SEQUENCE + eContentType ContentType, + eContent [0] EXPLICIT OCTET STRING OPTIONAL <---- + -- eContent within SignedData is TSTInfo + */ + CHK( mbedtls_asn1_get_tag( &p, end, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_CONTEXT_SPECIFIC) ); + + // this is the raw octed string that packages the bit that is 'signed' + // + CHK(mbedtls_asn1_get_tag( &p, end, &len, + MBEDTLS_ASN1_OCTET_STRING) ); + + // Signed area is within the octed string. This is the TS block + // that contains the digest of our plaintext. + // + signed_data = p; + signed_data_len = len; + + CHK(mbedtls_ts_get_tsinfo(&p, end, &(reply->ts_info))); + + // certificates [0] IMPLICIT CertificateSet OPTIONAL, + if (mbedtls_asn1_get_tag( &p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_CONTEXT_SPECIFIC) == 0) { + CHK(mbedtls_asn1_get_certificate_set(&p, p + len, chain)); + } else { + _TS_DEBUG_PRINTF("No Certs\n"); + }; + + if ( mbedtls_asn1_get_tag( &p, end, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_CONTEXT_SPECIFIC) == 0) { + _TS_DEBUG_PRINTF("Has CRLs(ignored)\n"); + p += len; + } else { + _TS_DEBUG_PRINTF("No CRLs\n"); + }; + + // signerInfos SignerInfos <-- + // SignerInfos ::= SET OF SignerInfo + CHK(mbedtls_asn1_get_tag( &p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SET) ); + + CHK(mbedtls_ts_get_signer_info(&p, end, &(reply->signer_info))); + + if (reply->signer_info.signing_cert_hash) { +#if _TS_DEBUG + _TS_DEBUG_PRINTF("Scanning for certs..\n"); + _TS_DEBUG_PRINTF(" hash (%d): ", reply->signer_info.signing_cert_hash_type); + for (int i = 0; i < reply->signer_info.signing_cert_hash_len; i++) { + _TS_DEBUG_PRINTF("%02x", reply->signer_info.signing_cert_hash[i]); + } + _TS_DEBUG_PRINTF(".\n"); +#endif + + // XXX set minimal level ? > SHA1 ? + const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type( reply->signer_info.signing_cert_hash_type ); + assert(md_info); + + if (reply->signer_info.signing_cert_hash_len != mbedtls_md_get_size(md_info)) { + _TS_DEBUG_PRINTF("Hash of cert / mismatch %d != %d\n", reply->signer_info.signing_cert_hash_len, mbedtls_md_get_size(md_info)); + ret = MBEDTLS_ERR_PK_SIG_LEN_MISMATCH; + goto ts_reply_free_and_exit; + } + + crt = *chain; + for (int d = 0; crt; crt = crt->next, d++) { + uint8_t digest[MBEDTLS_MD_MAX_SIZE]; + + mbedtls_md_context_t md; + mbedtls_md_init(&md); + mbedtls_md_setup(&md, md_info, 0); + + CHK(mbedtls_md_starts(&md)); + CHK(mbedtls_md_update(&md, crt->raw.p, crt->raw.len)); + CHK(mbedtls_md_finish(&md, digest)); + +#ifdef _TS_DEBUG + _TS_DEBUG_PRINTF(" %2d hash: ", d); + for (int i = 0; i < mbedtls_md_get_size(md_info); i++) { + _TS_DEBUG_PRINTF("%02x", digest[i]); + } + _TS_DEBUG_PRINTF(".\n "); + for (int i = 0; i < mbedtls_md_get_size(md_info); i++) { + _TS_DEBUG_PRINTF("%02x", reply->signer_info.signing_cert_hash[i]); + } + _TS_DEBUG_PRINTF(".\n"); +#endif + + if (bcmp(digest, reply->signer_info.signing_cert_hash, mbedtls_md_get_size(md_info)) == 0) { + _TS_DEBUG_PRINTF("Found one that matched\n"); + if (*chain != crt) { + // not at the head of the chain; now make it so. + mbedtls_x509_crt * parent; + for (parent = *chain; parent && parent->next != crt; parent = parent->next); + if (parent->next != crt) { + ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + goto ts_reply_free_and_exit; + } + parent->next = crt->next; // connect the parent to the tail of the chain. + crt->next = *chain; // connect this chain to myself. + *chain = crt; // and wire myself at the top. + } + break; + }; + _TS_DEBUG_PRINTF("Nope # %d did not match\n", d); + }; + }; + + + /* RFC 5652: Cryptographic Message Syntax (CMS) + + 5.4. Message Digest Calculation Process + + The message digest calculation process computes a message digest on + either the content being signed or the content together with the + signed attributes. In either case, the initial input to the message + digest calculation process is the "value" of the encapsulated content + being signed. Specifically, the initial input is the + encapContentInfo eContent OCTET STRING to which the signing process + is applied. Only the octets comprising the value of the eContent + OCTET STRING are input to the message digest algorithm, not the tag + or the length octets. + + The result of the message digest calculation process depends on + whether the signedAttrs field is present. When the field is absent, + the result is just the message digest of the content as described + above. When the field is present, however, the result is the message + digest of the complete DER encoding of the SignedAttrs value + contained in the signedAttrs field. Since the SignedAttrs value, + when present, must contain the content-type and the message-digest + attributes, those values are indirectly included in the result. The + content-type attribute MUST NOT be included in a countersignature + unsigned attribute as defined in Section 11.4. A separate encoding + of the signedAttrs field is performed for message digest calculation. + The IMPLICIT [0] tag in the signedAttrs is not used for the DER + encoding, rather an EXPLICIT SET OF tag is used. That is, the DER + encoding of the EXPLICIT SET OF tag, rather than of the IMPLICIT [0] + tag, MUST be included in the message digest calculation along with + the length and content octets of the SignedAttributes value. + + When the signedAttrs field is absent, only the octets comprising the + value of the SignedData encapContentInfo eContent OCTET STRING (e.g., + the contents of a file) are input to the message digest calculation. + This has the advantage that the length of the content being signed + need not be known in advance of the signature generation process. + + Although the encapContentInfo eContent OCTET STRING tag and length + octets are not included in the message digest calculation, they are + protected by other means. The length octets are protected by the + nature of the message digest algorithm since it is computationally + infeasible to find any two distinct message contents of any length + that have the same message digest. + + */ + // Calculate the digest over the signed data area. I.e. the timestamp + // and the digest of the plaintext. + // + mbedtls_md_init(&md); + + // XXX set minimal level ? > SHA1 ? + md_info = mbedtls_md_info_from_type( signed_data_digest_type ); + assert(md_info); + + signed_data_digest_len = mbedtls_md_get_size(md_info); + assert(signed_data_digest_len); + mbedtls_md_setup(&md, md_info, 0); + bzero(signed_data_digest, sizeof(signed_data_digest)); + assert(sizeof(signed_data_digest) == MBEDTLS_MD_MAX_SIZE); + + assert(signed_data); + assert(signed_data_len); + + CHK(mbedtls_md_starts(&md)); + CHK(mbedtls_md_update(&md, signed_data, signed_data_len)); + CHK(mbedtls_md_finish(&md, signed_data_digest)); + + +#if _TS_DEBUG + _TS_DEBUG_PRINTF("Calculated signedData area hash: "); + for (int i = 0; i < mbedtls_md_get_size(md_info); i++) { + _TS_DEBUG_PRINTF("%02x", signed_data_digest[i]); + } + _TS_DEBUG_PRINTF(".\n"); +#endif + + if (reply->signer_info.signed_attribute_raw && reply->signer_info.sig_digest) { + _TS_DEBUG_PRINTF("Indirected signing.\n"); + + if (reply->signer_info.sig_digest_len != signed_data_digest_len) { + _TS_DEBUG_PRINTF("Digest in signedAttrs has the wrong length %d != %d\n", reply->signer_info.sig_digest_len, signed_data_digest_len); + ret = MBEDTLS_ERR_PK_SIG_LEN_MISMATCH; + goto ts_reply_free_and_exit; + }; + + // XXX set minimal level ? > SHA1 ? + if (((reply->signer_info.sig_digest_type != signed_data_digest_type)) || (signed_data_digest_type == MBEDTLS_MD_NONE)) { + _TS_DEBUG_PRINTF("Digest in signedAttrs types do no match up"); + ret = MBEDTLS_ERR_X509_INVALID_SIGNATURE; + goto ts_reply_free_and_exit; + }; + + // verify that the indirect signing is about the digest of the area that contains + // the digest and plaintext of the plaintext + // + if (bcmp(signed_data_digest, reply->signer_info.sig_digest, reply->signer_info.sig_digest_len)) { + _TS_DEBUG_PRINTF("Digest in signedAttrs lengths do not match up"); + ret = MBEDTLS_ERR_PK_SIG_LEN_MISMATCH; + goto ts_reply_free_and_exit; + }; + + // And now verify that the hash of the area is in the signed section of + // which the signature is checked. + + assert(reply->signer_info.sig_digest_type); + + mbedtls_md_context_t md; + mbedtls_md_init(&md); + md_info = mbedtls_md_info_from_type(reply->signer_info.sig_digest_type); + mbedtls_md_setup(&md, md_info, 0); + + assert(md_info); + assert(reply->signer_info.signed_attribute_raw); + assert(reply->signer_info.signed_attribute_len); + + // We need to 'repair' the signed block before taking the hash. See above paragraph ! + unsigned char first_byte = MBEDTLS_ASN1_SET | MBEDTLS_ASN1_CONSTRUCTED; + CHK(mbedtls_md_starts(&md)); + CHK(mbedtls_md_update(&md, &first_byte, 1)); + CHK(mbedtls_md_update(&md, reply->signer_info.signed_attribute_raw + 1, reply->signer_info.signed_attribute_len - 1)); + CHK(mbedtls_md_finish(&md, signed_data_digest)); + }; + + assert(&(crt->pk)); + + assert(signed_data_digest_type); + assert(signed_data_digest_len); + + assert(reply->signer_info.sig); + assert(reply->signer_info.sig_len); + +#ifdef _TS_DEBUG + _TS_DEBUG_PRINTF("Attribute Signed Data (s=%d, l=%d) -- calculated digest: len=%d: ", + reply->signer_info.signed_attribute_raw - from, + reply->signer_info.signed_attribute_len, + signed_data_digest_len); + for (int i = 0; i < signed_data_digest_len; i++) { + _TS_DEBUG_PRINTF("%02x", signed_data_digest[i]); + } + _TS_DEBUG_PRINTF(".\n"); + + _TS_DEBUG_PRINTF("Signed info (%d bytes): ", reply->signer_info.sig_len); + for (int i = 0; i < reply->signer_info.sig_len; i++) { + _TS_DEBUG_PRINTF("%02x", reply->signer_info.sig[i]); + } + _TS_DEBUG_PRINTF(".\n"); +#endif + + ret = mbedtls_pk_verify(&(crt->pk), + signed_data_digest_type, signed_data_digest, signed_data_digest_len, + reply->signer_info.sig, reply->signer_info.sig_len); + switch (ret) { + case 0: + _TS_DEBUG_PRINTF("Signature on the Reply OK\n"); + break; + case MBEDTLS_ERR_RSA_VERIFY_FAILED: { + char buff[128]; mbedtls_strerror(ret, buff, sizeof(buff)); + _TS_DEBUG_PRINTF("Signature FAIL %x: %s\n", -ret, buff); + goto ts_reply_free_and_exit; + }; + break; + case MBEDTLS_ERR_PK_SIG_LEN_MISMATCH: { + char buff[128]; mbedtls_strerror(ret, buff, sizeof(buff)); + _TS_DEBUG_PRINTF("Signature config fail %x: %s\n", -ret, buff); + goto ts_reply_free_and_exit; + }; + break; + default: + printf("Other error %x %x\n", ret, -ret); + goto ts_reply_free_and_exit; + break; + }; + + // So all that is left to check now is if the cert used above - is indeed sufficiently trusted. + return (0); + +ts_reply_len_fault_and_exit: + ret = MBEDTLS_ERR_TS_REPLY_INVALID_FORMAT + MBEDTLS_ERR_ASN1_LENGTH_MISMATCH; + +ts_reply_free_and_exit: + mbedtls_ts_reply_free( reply ); + return ret; +} diff --git a/libraries/Update/src/mbedtls-ts-addons/ts.h b/libraries/Update/src/mbedtls-ts-addons/ts.h new file mode 100644 index 00000000000..8656c76891d --- /dev/null +++ b/libraries/Update/src/mbedtls-ts-addons/ts.h @@ -0,0 +1,247 @@ +#include "mbedtls/config.h" +#include "mbedtls/error.h" +#include "mbedtls/asn1.h" +#include "mbedtls/error.h" +#include "mbedtls/oid.h" +#include "mbedtls/config.h" +#include "mbedtls/error.h" +#include "mbedtls/asn1.h" +#include "mbedtls/error.h" +#include "mbedtls/oid.h" +#include "mbedtls/x509.h" +#include "mbedtls/x509_crt.h" +#include +#include + +#include + + +#include "x509_ts_utils.h" +#include "sign.h" + +#ifndef _H_TS +#define _H_TS +/* + TimeStampResp ::= SEQUENCE { + status PKIStatusInfo, + PKIStatusInfo ::= SEQUENCE { + status PKIStatus, + KIStatus ::= INTEGER { + granted (0), -- when the PKIStatus contains the value zero a TimeStampToken, as requested, is present. + grantedWithMods (1), -- when the PKIStatus contains the value one a TimeStampToken, with modifications, is present. + rejection (2), + waiting (3), + revocationWarning (4), -- this message contains a warning that a revocation is imminent + revocationNotification (5) -- notification that a revocation has occurred + } // end of PKIStatusInfo + statusString PKIFreeText OPTIONAL, + PKIFreeText ::= SEQUENCE SIZE (1..MAX) OF UTF8String + failInfo PKIFailureInfo OPTIONAL + PKIFailureInfo ::= BIT STRING { + badAlg (0),-- unrecognized or unsupported Algorithm Identifier + badRequest (2),-- transaction not permitted or supported + badDataFormat (5),-- the data submitted has the wrong format + timeNotAvailable (14),-- the TSA's time source is not available + unacceptedPolicy (15),-- the requested TSA policy is not supported by the TSA. + unacceptedExtension (16),-- the requested extension is not supported by the TSA. + addInfoNotAvailable (17),-- the additional information requested could not be understood or is not available + systemFailure (25)-- the request cannot be handled due to system failure + } // end of PKIFailureInfo + } // end of PKIStatusInfo + timeStampToken TimeStampToken OPTIONAL + TimeStampToken ::= ContentInfo + ContentInfo ::= SEQUENCE { + -- contentType is id-signedData as defined in [CMS] + contentType id-signedData OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs7(7) 2 }, + -- content is SignedData as defined in([CMS]) + content [0] EXPLICIT ANY DEFINED BY contentType + SignedData ::= SEQUENCE { + version CMSVersion, + digestAlgorithms DigestAlgorithmIdentifiers, + DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier + DigestAlgorithmIdentifier ::= AlgorithmIdentifier + encapContentInfo EncapsulatedContentInfo, + EncapsulatedContentInfo ::= SEQUENCE { + -- eContentType within SignedData is id-ct-TSTInfo + eContentType id-ct-TSTInfo OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) ct(1) 4}, + -- eContent within SignedData is TSTInfo + eContent [0] EXPLICIT OCTET STRING OPTIONAL + TSTInfo ::= SEQUENCE { + version INTEGER { v1(1) }, + policy TSAPolicyId, + TSAPolicyId ::= OBJECT IDENTIFIER + messageImprint MessageImprint, -- MUST have the same value as the similar field in TimeStampReq + MessageImprint ::= SEQUENCE { + hashAlgorithm AlgorithmIdentifier, + AlgorithmIdentifier ::= SEQUENCE { + algorithm OBJECT IDENTIFIER, + parameters ANY DEFINED BY algorithm OPTIONAL + } // end of algo identifier + hashedMessage OCTET STRING + } // end of MessageImprint + serialNumber INTEGER, -- Time-Stamping users MUST be ready to accommodate integers up to 160 bits. + genTime GeneralizedTime, -- YYMMDDHHMM[SS[.D*]Z[stuff] + accuracy Accuracy OPTIONAL, + Accuracy ::= SEQUENCE { + seconds INTEGER OPTIONAL, + millis [0] INTEGER (1..999) OPTIONAL, + micros [1] INTEGER (1..999) OPTIONAL + } // end of Accuracy + ordering BOOLEAN DEFAULT FALSE, + nonce INTEGER OPTIONAL, -- MUST be present if the similar field was present in TimeStampReq. In that case it MUST have the same value. + tsa [0] GeneralName OPTIONAL, + GeneralName ::= CHOICE { + otherName [0] OtherName, + rfc822Name [1] IA5String, + dNSName [2] IA5String, + x400Address [3] ORAddress, + directoryName [4] Name, + ediPartyName [5] EDIPartyName, + uniformResourceIdentifier [6] IA5String, + iPAddress [7] OCTET STRING, + registeredID [8] OBJECT IDENTIFIER + } // end of GeneralName + extensions [1] IMPLICIT Extensions OPTIONAL + Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension + Extension ::= SEQUENCE { + extnID OBJECT IDENTIFIER, + critical BOOLEAN DEFAULT FALSE, + extnValue OCTET STRING + } // Extension + // Extensions + } // end of eContent GeneralName + } // end of encapContentInfo + certificates [0] IMPLICIT CertificateSet OPTIONAL, + crls [1] IMPLICIT RevocationInfoChoices OPTIONAL, + signerInfos SignerInfos + SignerInfos ::= SET OF SignerInfo + SignerInfo ::= SEQUENCE { + version CMSVersion, + CMSVersion ::= INTEGER + sid SignerIdentifier, + SignerIdentifier ::= CHOICE { + issuerAndSerialNumber IssuerAndSerialNumber, + subjectKeyIdentifier [0] SubjectKeyIdentifier + } + digestAlgorithm DigestAlgorithmIdentifier, + signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL, + SignedAttributes ::= SET SIZE (1..MAX) OF Attribute + Attribute ::= SEQUENCE { + attrType OBJECT IDENTIFIER, + attrValues SET OF AttributeValue + AttributeValue ::= ANY + } + signatureAlgorithm SignatureAlgorithmIdentifier, + signature SignatureValue, + SignatureValue ::= OCTET STRING + unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL + UnsignedAttributes ::= SET SIZE (1..MAX) OF Attribute + Attribute ::= SEQUENCE { + attrType OBJECT IDENTIFIER, + attrValues SET OF AttributeValue + AttributeValue ::= ANY + } + } // SignerInfo + // SET of SignerInfo / SignerInfos + } // end of content (a SignedData) + } // end of ContentInfo + + } // end of TimeStampResp + + + + TSTInfo ::= SEQUENCE { + version INTEGER { v1(1) }, + policy TSAPolicyId, + messageImprint MessageImprint, + -- MUST have the same value as the similar field in + -- TimeStampReq + serialNumber INTEGER, + -- Time-Stamping users MUST be ready to accommodate integers + -- up to 160 bits. + genTime GeneralizedTime, + accuracy Accuracy OPTIONAL, + ordering BOOLEAN DEFAULT FALSE, + nonce INTEGER OPTIONAL, + -- MUST be present if the similar field was present + -- in TimeStampReq. In that case it MUST have the same value. + tsa [0] GeneralName OPTIONAL, + extensions [1] IMPLICIT Extensions OPTIONAL } + + Accuracy ::= SEQUENCE { + seconds INTEGER OPTIONAL, + millis [0] INTEGER (1..999) OPTIONAL, + micros [1] INTEGER (1..999) OPTIONAL } +*/ +#define MBEDTLS_ERR_TS_CORRUPTION_DETECTED -10000 +#define MBEDTLS_ERR_TS_BAD_INPUT_DATA -10001 +#define MBEDTLS_ERR_TS_REPLY_INVALID_FORMAT -10002 + +# define TS_STATUS_GRANTED 0 +# define TS_STATUS_GRANTED_WITH_MODS 1 +# define TS_STATUS_REJECTION 2 +# define TS_STATUS_WAITING 3 +# define TS_STATUS_REVOCATION_WARNING 4 +# define TS_STATUS_REVOCATION_NOTIFICATION 5 + +# define TS_INFO_BAD_ALG 0 +# define TS_INFO_BAD_REQUEST 2 +# define TS_INFO_BAD_DATA_FORMAT 5 +# define TS_INFO_TIME_NOT_AVAILABLE 14 +# define TS_INFO_UNACCEPTED_POLICY 15 +# define TS_INFO_UNACCEPTED_EXTENSION 16 +# define TS_INFO_ADD_INFO_NOT_AVAILABLE 17 +# define TS_INFO_SYSTEM_FAILURE 25 + +// Should be moved to oid.h +#define MBEDTLS_OID_PKCS7 MBEDTLS_OID_PKCS "\x07" /**< pkcs-7 OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 7 } */ +#define MBEDTLS_OID_PKCS7_ID_SIGNED MBEDTLS_OID_PKCS7 "\x02" /*< id-signedData OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs7(7) 2 } */ +#define MBEDTLS_OID_STINFO MBEDTLS_OID_PKCS9 "\x10" "\x01" "\x04" /* id-ct-TSTInfo OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) ct(1) 4} */ + +#define MBEDTLS_OID_PKCS9_MESSAGE_DIGEST MBEDTLS_OID_PKCS9 "\x04" /**< messageDigest AttributeType ::= { pkcs-9 4 } */ + +#define MBEDTLS_OID_PKCS9_SMIME MBEDTLS_OID_PKCS9 "\x10" //{iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) +#define MBEDTLS_OID_PKCS9_SMIME_AA_SIGNING_CERT MBEDTLS_OID_PKCS9_SMIME "\x02\x0C" // smime(16) id-aa(2) signing-certificate(12)} +#define MBEDTLS_OID_PKCS9_SMIME_AA_SIGNING_CERT_V2 MBEDTLS_OID_PKCS9_SMIME"\x02\x2F" // smime(16) id-aa(2) signing-certificateV2(47)} + +typedef mbedtls_asn1_buf mbedtls_ts_buf; + +// ASN1: PKIStatusInfo +// +typedef struct mbedtls_asn1_ts_pki_status_info { + int pki_status; + char * statusString; + int failInfo; +} mbedtls_asn1_ts_pki_status_info; + +// ASN1: TSTInfo (the RFC3161 timestamp specific eContent) +// +typedef struct mbedtls_asn1_ts_pki_ts_info { + mbedtls_md_type_t digest_type; + + unsigned char * payload_digest; + size_t payload_digest_len; + + mbedtls_x509_time signed_time; + +} mbedtls_asn1_ts_pki_ts_info; + +// ASN1: TimeStampResp +typedef struct mbedtls_ts_reply +{ + mbedtls_asn1_ts_pki_status_info status; + // mbedtls_asn1_ts_pki_time_stamp_token timestamptoken; + mbedtls_asn1_ts_pki_ts_info ts_info; + mbedtls_asn1_ts_pki_signer_info signer_info; + mbedtls_x509_crt * chain; +} mbedtls_ts_reply; + +int mbedtls_asn1_get_ts_pkistatusinfo(unsigned char **p, unsigned char * end, mbedtls_asn1_ts_pki_status_info * status); +int mbedtls_ts_get_tsinfo(unsigned char **p, unsigned char * end, mbedtls_asn1_ts_pki_ts_info * ts_info); +int mbedtls_x509_ts_reply_parse_der(const unsigned char **buf, size_t * buflen, mbedtls_ts_reply * reply); + +int mbedtls_ts_pki_status_info_free(mbedtls_asn1_ts_pki_status_info * status); +int mbedtls_ts_reply_free(struct mbedtls_ts_reply *reply); + +int mbedtls_ts_reply_free(struct mbedtls_ts_reply *reply); +#endif diff --git a/libraries/Update/src/mbedtls-ts-addons/x509_ts_utils.cpp b/libraries/Update/src/mbedtls-ts-addons/x509_ts_utils.cpp new file mode 100644 index 00000000000..0363c4ccb83 --- /dev/null +++ b/libraries/Update/src/mbedtls-ts-addons/x509_ts_utils.cpp @@ -0,0 +1,371 @@ + +#include "mbedtls/x509.h" +#include "mbedtls/x509_crt.h" +#include "x509_ts_utils.h" +#include +#include + +#define mbedtls_free free +#define mbedtls_calloc calloc +#define mbedtls_printf _TS_DEBUG_PRINTF +#define mbedtls_snprintf _TS_DEBUG_SNPRINTF + +#ifdef _TS_DEBUG +char * _oid2str(mbedtls_x509_buf *oid) { + static char s[256]; + s[0] = 0; + mbedtls_oid_get_numeric_string(s, sizeof(s), oid); + return s; +}; + +char * _oidbuff2str(unsigned char *buf, size_t len) { + static mbedtls_x509_buf oid; + oid.p = buf; + oid.len = len; + return _oid2str(&oid); +}; + +char * _bitstr(mbedtls_asn1_bitstring *bs) { + static char s[256]; + int i = 0; + for (; i < 8 * bs->len - bs->unused_bits && i < sizeof(s) - 1; i++) { + s[i] = bs->p[i >> 4] & (1 << (i & 7)) ? '1' : '0'; + } + s[i] = 0; + return s; +}; +#endif + + +int mbedtls_x509_get_names( unsigned char **p, const unsigned char *end, + mbedtls_x509_name *cur ) +{ + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + size_t set_len = 0; + + if ( ( mbedtls_asn1_get_tag(p, end, &set_len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) ) != 0) { + return ret; + }; + unsigned char * ep = *p + set_len; + + ret = mbedtls_x509_get_name(p, *p + set_len, cur); + if (ret) + return ret; + assert(ep == *p); + + return 0; +}; +#define CHECK(code) { if( ( ret = ( code ) ) != 0 ) return( ret ); } +#define SKIPLEADINGZERORS(p,len) while ( len > 0 && **p == 0 ) {++( *p );--len;} + +int mbedtls_asn1_get_uint64( unsigned char **p, + const unsigned char *end, + uint64_t *val ) +{ + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + size_t len; + + if ( ( ret = mbedtls_asn1_get_tag( p, end, &len, MBEDTLS_ASN1_INTEGER ) ) != 0 ) { + return ( ret ); + }; + unsigned char * ep = *p + len; + + /* + len==0 is malformed (0 must be represented as 020100 for INTEGER, + or 0A0100 for ENUMERATED tags + */ + if ( len == 0 ) + return ( MBEDTLS_ERR_ASN1_INVALID_LENGTH ); + + /* This is a cryptography library. Reject negative integers. */ + if ( ( **p & 0x80 ) != 0 ) + return ( MBEDTLS_ERR_X509_INVALID_FORMAT ); + + SKIPLEADINGZERORS(p, len); + + if ( len > sizeof( uint64_t ) ) + return ( MBEDTLS_ERR_ASN1_INVALID_LENGTH ); + + *val = 0; + while ( len-- > 0 ) + { + *val = ( *val << 8 ) | **p; + (*p)++; + } + + assert(ep == *p); + return ( 0 ); +} + +// Many specs allow /require at least 160 bits these days. +// +int mbedtls_asn1_get_serial_mpi( unsigned char **p, + const unsigned char *end, + mbedtls_mpi *val ) +{ + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + size_t len; + mbedtls_mpi_init(val); + + if ( ( ret = mbedtls_asn1_get_tag( p, end, &len, MBEDTLS_ASN1_INTEGER ) ) != 0 ) + return ( ret ); + unsigned char * ep = *p + len; + + /* + len==0 is malformed (0 must be represented as 020100 for INTEGER, + or 0A0100 for ENUMERATED tags + */ + if ( len == 0 ) + return ( MBEDTLS_ERR_ASN1_INVALID_LENGTH ); + + /* Serials cannot be negative.. */ + if ( ( **p & 0x80 ) != 0 ) + return ( MBEDTLS_ERR_ASN1_INVALID_DATA ); + val->s = 1; + + SKIPLEADINGZERORS(p, len); + + while ( len-- > 0 ) + { + if ((ret = mbedtls_mpi_lset(val, **p)) != 0) + return ret; + if ((ret = mbedtls_mpi_shift_l(val, 8)) != 0) + return ret; + (*p)++; + } + + assert(ep == *p); + + return ( 0 ); +} + +int mbedtls_asn1_get_serial_bitstring( unsigned char **p, + const unsigned char *end, + mbedtls_asn1_bitstring *val ) +{ + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + size_t len; + + if ( ( ret = mbedtls_asn1_get_tag( p, end, &len, MBEDTLS_ASN1_INTEGER ) ) != 0 ) + return ( ret ); + unsigned char * ep = *p + len; + + /* + len==0 is malformed (0 must be represented as 020100 for INTEGER, + or 0A0100 for ENUMERATED tags + */ + if ( len == 0 ) + return ( MBEDTLS_ERR_ASN1_INVALID_LENGTH ); + + /* Serials cannot be negative.. */ + if ( ( **p & 0x80 ) != 0 ) + return ( MBEDTLS_ERR_ASN1_INVALID_DATA ); + + SKIPLEADINGZERORS(p, len); + + val->unused_bits = 0; + val->len = len; + val->p = *p; + + *p += len; + + assert(ep == *p); + return ( 0 ); +} + +int x509_parse_int( unsigned char **p, size_t n, int *res ) +{ + *res = 0; + + for ( ; n > 0; --n ) + { + if ( ( **p < '0') || ( **p > '9' ) ) + return ( MBEDTLS_ERR_X509_INVALID_DATE ); + + *res *= 10; + *res += ( *(*p)++ - '0' ); + } + + return ( 0 ); +} +int x509_parse_time( unsigned char **p, size_t len, size_t yearlen, + mbedtls_x509_time *tm ) +{ + int x509_parse_int( unsigned char **p, size_t n, int *res ); + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + /* + Minimum length is 10 or 12 depending on yearlen + */ + if ( len < yearlen + 8 ) + return ( MBEDTLS_ERR_X509_INVALID_DATE ); + + len -= yearlen + 8; + + /* + Parse year, month, day, hour, minute + */ + CHECK( x509_parse_int( p, yearlen, &tm->year ) ); + if ( 2 == yearlen ) + { + if ( tm->year < 50 ) + tm->year += 100; + + tm->year += 1900; + } + + CHECK( x509_parse_int( p, 2, &tm->mon ) ); + CHECK( x509_parse_int( p, 2, &tm->day ) ); + CHECK( x509_parse_int( p, 2, &tm->hour ) ); + CHECK( x509_parse_int( p, 2, &tm->min ) ); + + /* + Parse seconds if present + */ + if ( len >= 2 ) + { + CHECK( x509_parse_int( p, 2, &tm->sec ) ); + len -= 2; + + /* Parse any fractional seconds + */ + if (len > 1 && (*p)[0] == '.') { + float millis = 0, d = 0.1; + (*p)++; len--; + + while (len && isdigit((*p)[0])) { + int k = ((*p)[0]) - '0'; + millis += k * d; + d /= 10.; + (*p)++; len--; + } + } + }; + + /* + Parse trailing 'Z' if present + */ + if ( len > 0 && 'Z' == **p ) + { + (*p)++; + len--; + + } + // sometime we get some timezone cruft. + // skipp all that. + if (len) { + (*p) += len; + len = 0; + }; + + if ( 0 != len ) { + _TS_DEBUG_PRINTF("At the end with %d left %c %c %c\n", len, (*p)[0], (*p)[1], (*p)[2]); + return ( MBEDTLS_ERR_X509_INVALID_DATE ); + }; + // CHECK( x509_date_is_valid( tm ) ); + + return ( 0 ); +} + +int mbedtls_asn1_get_algorithm_itentifier(unsigned char **p, unsigned char * end, int * alg) +{ + size_t len; + int ret; + *alg = -1; + + if ( ( ret = mbedtls_asn1_get_tag(p, end, &len, MBEDTLS_ASN1_OID) ) != 0 ) { + assert(0); + return ( MBEDTLS_ERR_X509_INVALID_FORMAT ); + } + unsigned char * ep = *p + len; + + mbedtls_x509_buf oid; + oid.p = *p; + oid.len = len; + _TS_DEBUG_PRINTF("algoritm OID: %s\n", _oid2str(&oid)); + + mbedtls_md_type_t md_alg; + mbedtls_pk_type_t pk_alg; + + if ( ( ret = mbedtls_oid_get_md_alg(&oid, &md_alg)) == 0) { + _TS_DEBUG_PRINTF(" MD algoritm internal num: %d %s\n", md_alg, mbedtls_md_get_name(mbedtls_md_info_from_type(md_alg))); + *alg = (int) md_alg; + } else if ( ( ret = mbedtls_oid_get_pk_alg(&oid, &pk_alg)) == 0) { + _TS_DEBUG_PRINTF(" PK algoritm internal num: %d\n", pk_alg); //, mbedtls_pk_get_name(mbedtls_pk_info_from_type(pk_alg))); + *alg = (int) pk_alg; + } else { + _TS_DEBUG_PRINTF("Unkn OID"); + *alg = -1; + } + *p += len; + + assert(ep == *p); + return 0; +} + +int mbedtls_asn1_get_algorithm_itentifiers(unsigned char **p, unsigned char * end, int * alg) +{ + size_t len; + int ret; + *alg = -1; + + if ( ( ret = mbedtls_asn1_get_tag( p, end, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE ) ) != 0 ) { + assert(0); + return ( MBEDTLS_ERR_X509_INVALID_FORMAT ); + } + unsigned char * ep = *p + len; + + while (*p < ep) { + if ( mbedtls_asn1_get_tag(p, end, &len, MBEDTLS_ASN1_NULL) == 0 ) { + break; + }; + int out = -1; + if (0 == mbedtls_asn1_get_algorithm_itentifier(p, end, &out) && out != -1) + *alg = out; + }; + + assert(ep == *p); + return 0; +} + +int mbedtls_asn1_get_certificate_set(unsigned char **p, unsigned char * end, mbedtls_x509_crt ** chain) { + size_t len = end - *p; + + // We allow for this function to be called multiple times (e.g. for a multi-party + // nested signature in S/MIME). + // + if (*chain == NULL) { + if ((*chain = (mbedtls_x509_crt *)mbedtls_calloc( 1, sizeof(mbedtls_x509_crt))) == NULL) + return MBEDTLS_ERR_X509_ALLOC_FAILED; + mbedtls_x509_crt_init(*chain); + }; + + while (mbedtls_x509_crt_parse_der(*chain, *p, len) == 0) { /* and end check !*/ + // We keep them in the order as given on the wire. This means that usually + // the leaf is first; followed by the chain to the top. But not always - quite a + // few german institutions do it the other way round. + // + // But given that some callers will need this set to be ordered - we do not try + // to order them 'right' just yet. + // + mbedtls_x509_crt * crt; + for (crt = *chain; crt->next; crt = crt->next); + + // Bit of a cheat - we do not get the actual legth eaten returned; so + // get it from the raw length. + // + *p += crt->raw.len; + len -= crt->raw.len; + + }; /* while there are still certs in the sequence */ + +#ifdef _TS_DEBUG + int i = 0; + for (mbedtls_x509_crt * crt = *chain; crt; crt = crt->next) i++; + _TS_DEBUG_PRINTF("Extracted %d certs\n", i); +#endif + + assert(end == *p); + return 0; +}; diff --git a/libraries/Update/src/mbedtls-ts-addons/x509_ts_utils.h b/libraries/Update/src/mbedtls-ts-addons/x509_ts_utils.h new file mode 100644 index 00000000000..34bd2f71547 --- /dev/null +++ b/libraries/Update/src/mbedtls-ts-addons/x509_ts_utils.h @@ -0,0 +1,47 @@ +#include "mbedtls/config.h" +#include "mbedtls/error.h" +#include "mbedtls/asn1.h" +#include "mbedtls/oid.h" +#include "mbedtls/x509.h" +#include "mbedtls/x509_crt.h" + +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE +#define _TS_DEBUG jipjip +#endif + +#ifndef _X509_TS_UTILS +#define _X509_TS_UTILS + +int mbedtls_asn1_get_serial_bitstring( unsigned char **p, + const unsigned char *end, + mbedtls_asn1_bitstring *val ); +int mbedtls_x509_get_names( unsigned char **p, const unsigned char *end, + mbedtls_x509_name *cur ); +int mbedtls_asn1_get_uint64( unsigned char **p, + const unsigned char *end, + uint64_t *val ); +int mbedtls_asn1_get_serial_mpi( unsigned char **p, + const unsigned char *end, + mbedtls_mpi *val ); +int mbedtls_asn1_get_algorithm_itentifier(unsigned char **p, unsigned char * end, int * alg); +int mbedtls_asn1_get_algorithm_itentifiers(unsigned char **p, unsigned char * end, int * alg); +int mbedtls_asn1_get_certificate_set(unsigned char **p, unsigned char * end, mbedtls_x509_crt ** chain); + +int x509_parse_int( unsigned char **p, size_t n, int *res ); +int x509_parse_time( unsigned char **p, size_t len, size_t yearlen, + mbedtls_x509_time *tm ); + +#ifndef MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED +#define MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED -0x006E /**< This is a bug in the library */ +#endif + +#ifdef _TS_DEBUG +char * _oid2str(mbedtls_x509_buf *oid); +char * _oidbuff2str(unsigned char *buf, size_t len); +char * _bitstr(mbedtls_asn1_bitstring *bs); +#define _TS_DEBUG_PRINTF(...) printf( __VA_ARGS__ ) +#else +#define _TS_DEBUG_PRINTF(...) /* no debugging compiled in */ +#endif + +#endif diff --git a/tools/espota.py b/tools/espota.py index 77fcfd75c64..9faf479bb14 100755 --- a/tools/espota.py +++ b/tools/espota.py @@ -36,6 +36,9 @@ import logging import hashlib import random +import errno +from time import time +import tempfile # Commands FLASH = 0 @@ -69,7 +72,7 @@ def update_progress(progress): sys.stderr.write('.') sys.stderr.flush() -def serve(remoteAddr, localAddr, remotePort, localPort, password, filename, command = FLASH): +def serve(remoteAddr, localAddr, remotePort, localPort, password, filename, command = FLASH, md = hashlib.new('MD5'), tsurl = None, amd_type = 'MD5', tskey = None): # Create a TCP/IP socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_address = (localAddr, localPort) @@ -82,11 +85,104 @@ def serve(remoteAddr, localAddr, remotePort, localPort, password, filename, comm return 1 content_size = os.path.getsize(filename) + + tsr = None + if tsurl or tskey: + try: + from pyasn1.codec.der import encoder + import rfc3161ng + + timestamper = rfc3161ng.RemoteTimestamper(tsurl,hashname='sha256',include_tsa_certificate=True) + f = open(filename,'rb') + tsr = timestamper(data=f.read(), return_tsr=True) + f.close() + + tsr = encoder.encode(tsr) + len_tsr = 0 + len(tsr) + + hdr = 'RedWax/1.00 rfc3161=%d payload=%d\n' % (len_tsr, content_size) + content_size = content_size + len(hdr) + len_tsr + + md.update(hdr) + md.update(tsr); + + except Exception,e: + sys.stderr.write('rfc3161ng not installed, falling back to openssl\n') + sys.stderr.flush() + rqh,rqn = tempfile.mkstemp(text=True) + rph,rpn = tempfile.mkstemp(text=True) + + cmd = "openssl ts -query -data '%s' -cert -sha256 -no_nonce -out '%s'" % (filename,rqn) + os.system(cmd) + if tsurl: + cmd = "curl -s -H 'Content-type: application/timestamp-query' --data-binary '@{}' '{}' > '{}'".format(rqn, tsurl, rpn) + os.system(cmd) + else: + tmph,tmpn = tempfile.mkstemp(text=True) + with os.fdopen(tmph,"w") as f: + f.write("0000\n") + f.close() + tsch,tscn = tempfile.mkstemp(text=True) + with os.fdopen(tsch,"w") as f: + f.write(""" +[ tsa ] +default_tsa = tsa_config + +[ tsa_config ] +serial = {} +crypto_device = builtin +signer_digest = sha256 +default_policy = 1.2 +other_policies = 1.2 +digests = sha256 +accuracy = secs:1 +ess_cert_id_alg = sha1 + +""".format(tmpn)) + f.close() + cmd = "openssl ts -reply -queryfile '{}' -signer '{}' -inkey '{}' -out {} -config '{}'".format(rqn, tskey, tskey, rpn, tscn) + print(cmd) + os.system(cmd) + + os.unlink(tmpn) + os.unlink(tscn) + + f = open(rpn,'rb') + tsr = f.read() + f.close() + + os.unlink(rqn) + os.unlink(rpn) + + len_tsr = 0 + len(tsr) + hdr = 'RedWax/1.00 rfc3161=%d payload=%d\n' % (len_tsr, content_size) + content_size = content_size + len(hdr) + len_tsr + + md.update(hdr) + md.update(tsr); + f = open(filename,'rb') - file_md5 = hashlib.md5(f.read()).hexdigest() + md.update(f.read()) f.close() - logging.info('Upload size: %d', content_size) - message = '%d %d %d %s\n' % (command, localPort, content_size, file_md5) + + file_md = md.hexdigest() + logging.info('Upload size: %d (%s: %s)', content_size, md.name, file_md) + message = '%d %d %d %s\n' % (command, localPort, content_size, file_md) + try: + amd = hashlib.new(amd_type) + except: + sys.stderr.write('Unknown Authentication hash type\n') + sys.stderr.flush() + return 1 + + if tsr != None or amd.name.upper() != 'MD5': + message = 'RedWax/1.00 cmd=%d port=%d size=%d md=%s digest=%s' % (command, localPort, content_size, md.name.upper(), file_md.upper()) + if amd.name.upper() != 'MD5': + message = message + ' authmd=%s' % (md.name.upper()) + if tsr != None: + message = message + ' rfc3161=%d' % (len(tsr)) + message = message + '\n' + logging.debug('Header: %s' % (message.strip())) # Wait for a connection inv_trys = 0 @@ -122,11 +218,22 @@ def serve(remoteAddr, localAddr, remotePort, localPort, password, filename, comm if (data != "OK"): if(data.startswith('AUTH')): nonce = data.split()[1] - cnonce_text = '%s%u%s%s' % (filename, content_size, file_md5, remoteAddr) - cnonce = hashlib.md5(cnonce_text.encode()).hexdigest() - passmd5 = hashlib.md5(password.encode()).hexdigest() + cnonce_text = '%s%u%s%s' % (filename, content_size, file_md, remoteAddr) + + amd = hashlib.new(amd_type) + amd.update(cnonce_text.encode()) + cnonce = amd.hexdigest() + + amd = hashlib.new(amd_type) + amd.update(password.encode()) + passmd5 = amd.hexdigest() + result_text = '%s:%s:%s' % (passmd5 ,nonce, cnonce) - result = hashlib.md5(result_text.encode()).hexdigest() + + amd = hashlib.new(amd_type) + amd.update(result_text.encode()) + result = amd.hexdigest() + sys.stderr.write('Authenticating...') sys.stderr.flush() message = '%d %s %s\n' % (AUTH, cnonce, result) @@ -152,16 +259,23 @@ def serve(remoteAddr, localAddr, remotePort, localPort, password, filename, comm return 1 sock2.close() - logging.info('Waiting for device...') + logging.info('Waiting for device to connect to us...') try: sock.settimeout(10) connection, client_address = sock.accept() sock.settimeout(None) - connection.settimeout(None) + connection.settimeout(1) + except: logging.error('No response from device') sock.close() return 1 + logging.debug('device contacted us over TCP and is connected.') + + if tsr != None: + connection.sendall(hdr) + logging.debug("Send Payload Header: {}".format(hdr.strip())) + try: f = open(filename, "rb") if (PROGRESS): @@ -170,24 +284,49 @@ def serve(remoteAddr, localAddr, remotePort, localPort, password, filename, comm sys.stderr.write('Uploading') sys.stderr.flush() offset = 0 - while True: - chunk = f.read(1024) - if not chunk: break - offset += len(chunk) - update_progress(offset/float(content_size)) - connection.settimeout(10) + + if tsr != None: try: - connection.sendall(chunk) - res = connection.recv(10) - lastResponseContainedOK = 'OK' in res.decode() + connection.settimeout(20) + l=connection.sendall(tsr) + logging.info("Send {} bytes TSR, payload is next".format(len(tsr))) except: sys.stderr.write('\n') - logging.error('Error Uploading') + logging.error('Error Uploading TSR') connection.close() f.close() sock.close() return 1 + # 10 second timeout; nonblocking + t = time() + lastResponseContainedOK = 0 + while time() - t < 10: + chunk = f.read(1024) + if not chunk: + break + + offset += len(chunk) + update_progress(offset/float(content_size)) + try: + connection.setblocking(1) + connection.sendall(chunk) + connection.setblocking(0) + + res = connection.recv(10) + if res != None: + t = time() + lastResponseContainedOK = 'OK' in res.decode() + + except socket.error, e: + if e.args[0] != errno.EWOULDBLOCK: + sys.stderr.write('\n') + logging.error('Error Uploading: {} '.format(e)) + connection.close() + f.close() + sock.close() + return 1 + if lastResponseContainedOK: logging.info('Success') connection.close() @@ -197,13 +336,12 @@ def serve(remoteAddr, localAddr, remotePort, localPort, password, filename, comm sys.stderr.write('\n') logging.info('Waiting for result...') + connection.setblocking(1) + connection.settimeout(1) + t = time() try: - count = 0 - while True: - count=count+1 - connection.settimeout(60) + while time() - t < 60: data = connection.recv(32).decode() - logging.info('Result: %s' ,data) if "OK" in data: logging.info('Success') @@ -211,12 +349,13 @@ def serve(remoteAddr, localAddr, remotePort, localPort, password, filename, comm f.close() sock.close() return 0; - if count == 5: - logging.error('Error response from device') - connection.close() - f.close() - sock.close() - return 1 + + logging.error('Timeout from device / no OK received') + connection.close() + f.close() + sock.close() + return 1 + except e: logging.error('No Result!') connection.close() @@ -275,6 +414,18 @@ def parser(unparsed_args): action = "store", default = "" ) + group.add_option("-M", "--auth-code", + dest = "auth", + help = "Set authentication password in hex-digest form; i.e. the obfuscated format", + action = "store", + default = "" + ) + group.add_option("-A", "--authentication-digest", + action = "store", + help = "Digest to use for authentication (e.g. md5(default), sha256, etc)", + dest = 'amd', + default = "md5" + ) parser.add_option_group(group) # image @@ -315,6 +466,30 @@ def parser(unparsed_args): ) parser.add_option_group(group) + group = optparse.OptionGroup(parser, "Firmware checksum/signature") + group.add_option("-m", "--digest", + action = "store", + help = "Digest to use to protect the firmware; (e.g. md5(default), sha256, etc)", + default = "md5" + ) + group.add_option("-S", "--sign", + dest = 'tsurl', + action = "store", + help = "Sign the firmware using the rfc3161 timeserver URL provided. E.g. https://interop.redwax.eu/test/timestamp. Cannot be combined with --signing-key", + ) + group.add_option("-K", "--signing-key", + dest = 'tskey', + action = "store", + help = "Sign the firmware using the key (PEM format) provided. Cannot be combined with --sign" + ) + group.add_option("-G", "--generate-signing-key", + dest = 'tsgen', + action = "store_true", + help = "Generate a PEM signing key, requires the --signing-key to be also specified" + ) + + parser.add_option_group(group) + (options, args) = parser.parse_args(unparsed_args) return options @@ -337,6 +512,35 @@ def main(args): global TIMEOUT TIMEOUT = options.timeout + if (options.tsgen and not options.tskey): + logging.critical("--signing-key required when using --generate-signing-key") + return 1 + + if (options.tsgen and options.tsurl): + logging.critical("options --signing-key and --sign (url) cannot be combined.") + return 1 + + if (options.tsgen): + cmd = "openssl req -x509 -subj=/CN=ArduinoOTA -keyout '{}' -out '{}' -nodes -sha256 --addext 'extendedKeyUsage=critical,timeStamping' ".format(options.tskey,options.tskey) + os.system(cmd) + + tmph,tmpn= tempfile.mkstemp() + cmd = "openssl x509 -in '{}' -outform DER -out '{}'".format(options.tskey, tmpn) + os.system(cmd) + + print("const unsigned char signing_cert[] = { ") + with open(tmpn,'rb') as f: + keybytes = f.read() + + print(','.join('0x{:02x}'.format(ord(b)) for b in keybytes)) + print("}; // end of signing cert") + + os.unlink(tmpn) + + if (not options.esp_ip or not options.image): + return 0 + return 1 + if (not options.esp_ip or not options.image): logging.critical("Not enough arguments.") return 1 @@ -345,9 +549,18 @@ def main(args): if (options.spiffs): command = SPIFFS - return serve(options.esp_ip, options.host_ip, options.esp_port, options.host_port, options.auth, options.image, command) + md = None + if (options.digest): + try: + md = hashlib.new(options.digest) + except: + logging.critical("Digest <%s> unknown.", options.digest) + return 1 + + return serve(options.esp_ip, options.host_ip, options.esp_port, options.host_port, options.auth, options.image, command, md, options.tsurl, options.amd, options.tskey) # end main if __name__ == '__main__': sys.exit(main(sys.argv)) + From bc684186232993e67bd3be38847a735c2776ca82 Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Sun, 19 Apr 2020 21:15:04 +0200 Subject: [PATCH 03/24] Fix for MD parser github --- tools/digital-signing.md | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/tools/digital-signing.md b/tools/digital-signing.md index 6b8dc4d2c31..5603b85c1fe 100644 --- a/tools/digital-signing.md +++ b/tools/digital-signing.md @@ -1,4 +1,4 @@ -Digital Signing of Over The Air (OTA) updates. +# Digital Signing of Over The Air (OTA) updates. The espota.py tools contains support for signed, over the air, firmware updates. This works by @@ -38,8 +38,7 @@ it allows you to manage signatures over long periods of time, even staff comes and leaves, keys need to be recycled or are lost. - -A) I just want to play. How do I do that ? +## A) I just want to play. How do I do that ? Take the example 'SecureOTA'. @@ -60,19 +59,19 @@ role of that interop.redwax.eu server would be a service in your own environment (we'll get to why that is a good idea later). So to install you run: - ./espota.py -i -f .../SecureOTA.bin \ + ./espota.py -i -f .../SecureOTA.bin \ --sign=https://interop.redwax.eu/test/timestamp while you watch the serial output. -B) OK - so how do I this secure, without any server noncense ? +## B) OK - so how do I this secure, without any server noncense ? So above example is just that; with no real security. Again takethe example 'SecureOTA' and open it. The next step would be to generate a public/private key; by doing - ./espota.py --signing-key=secret-signing-key.pem --generate-signing-key + ./espota.py --signing-key=secret-signing-key.pem --generate-signing-key this will write out the secret file to disk; but also show you the public (i.e. the non secret key) in an easy to include in @@ -81,7 +80,7 @@ paste it into the SecureOTA.ino example. Now change or add a line that says: - signatureChecker.addTrustedCertAsDER(signing_cert, sizeof(signing_cert)); + signatureChecker.addTrustedCertAsDER(signing_cert, sizeof(signing_cert)); If you leave in the existing line - then the Uploader will accept both your key and RedWax signed (insecure ones). Now tranfer this by serial or @@ -95,10 +94,10 @@ Once it is in - you can use your own key (in the file ) to sign and upload: - ./espota.py -i -f .../SecureOTA.bin \ - --signing-key=secret-signing-key.pem + ./espota.py -i -f .../SecureOTA.bin \ + --signing-key=secret-signing-key.pem -C) So what happens if I loose that private key. +## C) So what happens if I loose that private key. Well then, if the firmware which is in the ESP32 only has the signing_cert sent to signatureChecker.addTrustedCertAsDER, then it will only accept @@ -115,7 +114,7 @@ them or that someone else gets access to it. And the latter is always a bit of an issue; as you need this key everytime you do a test, a compile, etc. -D) That sounds a bit messy in an enterprise / serious setting. +## D) That sounds a bit messy in an enterprise / serious setting. That is indeed the case. So this is what PKI has been invented for. What you do here is that you use a hierarchy of signatures. So suppose From 91464ee90411032f044971468bc4a025e3e0f3ba Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Sun, 19 Apr 2020 22:04:21 +0200 Subject: [PATCH 04/24] Add some output as an example --- tools/digital-signing.md | 138 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/tools/digital-signing.md b/tools/digital-signing.md index 5603b85c1fe..35d8081d749 100644 --- a/tools/digital-signing.md +++ b/tools/digital-signing.md @@ -159,3 +159,141 @@ in an HSM are common choises), have important keys, such as those for production, well contained in a time-server. Yet easily accessible for those who need regular access. + +## Example output (Method A) + +With logging set to 'verbose + +1) normal boot + + rst:0xc (SW_CPU_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT) + configsip: 0, SPIWP:0xee + clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 + mode:DIO, clock div:1 + load:0x3fff0018,len:4 + load:0x3fff001c,len:1044 + load:0x40078000,len:8896 + load:0x40080400,len:5816 + entry 0x400806ac + Booting Apr 19 2020 21:55:30 + [D][WiFiGeneric.cpp:337] _eventCallback(): Event: 0 - WIFI_READY + [D][WiFiGeneric.cpp:337] _eventCallback(): Event: 2 - STA_START + [D][WiFiGeneric.cpp:337] _eventCallback(): Event: 4 - STA_CONNECTED + [D][WiFiGeneric.cpp:337] _eventCallback(): Event: 7 - STA_GOT_IP + [D][WiFiGeneric.cpp:381] _eventCallback(): STA IP: 10.11.0.193, MASK: 255.255.255.0, GW: 10.11.0.1 + [I][SecureArduinoOTA.cpp:153] begin(): OTA server at: ota-test.local:3232 + Ready + IP address: 10.11.0.193 + +2) ESP32 up - inbound oTA rquest + + [I][SecureArduinoOTA.cpp:292] _onRxHeaderPhase(): OTA Outer Digest: MD5: D28374F44E8B358E189FAF4FB6AFB8FF + [D][SecureUpdater.cpp:169] begin(): OTA Partition: app1 + Updating sketch + +3) Start of signature validation + + [D][SecureUpdateProcessors.cpp:202] process_header(): RFC 3161 signed payload + [D][SecureUpdateProcessors.cpp:231] process_header(): Processing RFC 3161 signed payload + + PKIStatus: 0 + idSig: 9 1.2.840.113549.1.7.2 + SignedData Version 3 + SignedData DigestAlgorithmIdentifier:algoritm OID: 2.16.840.1.101.3.4.2.1 + MD algoritm internal num: 6 SHA256 + SignedData DigestAlgorithmIdentifier: 6eContentType: 11 1.2.840.113549.1.9.16.1.4 + TSTInfo Version 1 + algoritm OID: 2.16.840.1.101.3.4.2.1 + MD algoritm internal num: 6 SHA256 + The main hash of the payload: len=32: f8baadde2339ebbed...6c31e743496043d33f1f. + Serial 16883855617417564721 + Signature timestap: 2020-04-19 19:57:58 UTC + Accuracy 1.0.0 (ignored 0 bytes) + Extracted 1 certs + No CRLs + SID name CN=Redwax Interop Testing Root Certificate Authority 2040, O=Redwax Project + DigestAlgorithmIdentifier:algoritm OID: 2.16.840.1.101.3.4.2.1 + MD algoritm internal num: 6 SHA256 + Signed attributes: + 1.2.840.113549.1.9.3: + Not decoded -- skipped + 1.2.840.113549.1.9.5: + Not decoded -- skipped + 1.2.840.113549.1.9.16.2.12: + Signing cert hash V1 (4): ff4237eaedc05da815c24db853f0d2bfda34da5c. + 1.2.840.113549.1.9.4: + Digest (signed section): len=32: 7bc441fcc129d9a4a90e5ed....784fb4c59f4d2216af. + SignatureAlgorithmIdentifier:algoritm OID: 1.2.840.113549.1.1.1 + PK algoritm internal num: 1 + Signature (512 bytes): 075fc499f569997fbea2d66e7d8c6cdae8....a8912bb3f54ee91f01df8b. + 0 hash: ff4237eaedc05da815c24db853f0d2bfda34da5c. + Found one that matched + Indirected signing. + Attribute Signed Data (s=1629, l=155) -- calculated digest: len=32: 69ef99b2713c41e6...099aeded0c45b6bedb. + Signature on the Reply OK + + + + + 4) Processing of the signature + [D][SecureUpdateProcessors.cpp:246] process_header(): Processed RFC 3161 signed payload + [I][SecureUpdateProcessors.cpp:254] process_header(): Processing plaintext with SHA256 specified RFC3161 digest. + [D][SecureUpdateProcessors.cpp:265] process_header(): RFC3161 signature verified. + [D][SecureUpdateProcessors.cpp:274] process_header(): Signatures in the trust chain: + [D][SecureUpdateProcessors.cpp:278] process_header(): - cert. version : 3 + - serial number : 6F:11:B7:D8:55:D2:7D:9A:14:F3:B6:E9:15:2B:60:CA:8C:4B:E2:AA + - issuer name : CN=Redwax Interop Testing Root Certificate Authority 2040, O=Redwax Project + - subject name : CN=Redwax Interop Testing Root Certificate Authority 2040, O=Redwax Project + - issued on : 2020-02-11 16:38:56 + - expires on : 2040-02-06 16:38:56 + - signed using : RSA with SHA1 + - RSA key size : 2048 bits + - basic constraints : CA=true + + [D][SecureUpdateProcessors.cpp:281] process_header(): Signatures in the RFC3161 wrapper: + [D][SecureUpdateProcessors.cpp:285] process_header(): - cert. version : 3 + - serial number : 05 + - issuer name : CN=Redwax Interop Testing Root Certificate Authority 2040, O=Redwax Project + - subject name : C=NL, ST=Zuid-Holland, L=Leiden, O=TimeServices, CN=Redwax Interop Test + - issued on : 2020-02-15 20:51:52 + - expires on : 2040-02-10 20:51:52 + - signed using : RSA with SHA-256 + - RSA key size : 4096 bits + - basic constraints : CA=false + - key usage : Digital Signature + - ext key usage : Time Stamping + + [I][SecureUpdateProcessors.cpp:293] process_header(): RFC3161 signature on timestamp and payload digest verified. + [D][SecureUpdateProcessors.cpp:303] process_header(): RFC3161: processing payload. + [D][SecureUpdateProcessors.cpp:51] process_header(): Valid magic at start of flash header + [D][SecureUpdateProcessors.cpp:362] process_end(): RFC3161 Finalizing payload digest + [D][SecureUpdateProcessors.cpp:382] process_end(): Payload calculated SHA256 Digest 32:f8baadde2339ebbed1b3afad3d6a9ad3cee69555f6c31e743496043d33f1f + [D][SecureUpdateProcessors.cpp:387] process_end(): RFC3161 Receveived SHA256 Digest 32:f8baadde2339ebbed1b3afad3d6a9ad3cee69555f6c31e743496043d33f1f + [I][SecureUpdateProcessors.cpp:402] process_end(): RFC3161 Payload digest matches signed digest. + [D][SecureUpdater.cpp:400] end(): Reporting an OK back up the chain + [D][SecureArduinoOTA.cpp:561] _runUpdate(): OTA Outer Digest matched. + [D][SecureUpdateProcessors.cpp:131] reset(): RESET + [D][SecureUpdater.cpp:359] activate(): RAW SHA256 Digest 361ab6322fa9e7a7bb23818d839e1bddafdf4735426edd297aedb9f6202bae + +5) Final phase - ESP 32 partition activated and rebooted. + + [D][SecureUpdater.cpp:211] abort(): Aborted. + [D][SecureUpdateProcessors.cpp:131] reset(): RESET + [D][WiFiGeneric.cpp:337] _eventCallback(): Event: 3 - STA_STOP + [D][WiFiGeneric.cpp:337] _eventCallback(): Event: 3 - STA_STOP + ets Jun 8 2016 00:22:57 + + rst:0xc (SW_CPU_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT) + configsip: 0, SPIWP:0xee + clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 + mode:DIO, clock div:1 + load:0x3fff0018,len:4 + load:0x3fff001c,len:1044 + load:0x40078000,len:8896 + load:0x40080400,len:5816 + entry 0x400806ac + Booting Apr 19 2020 21:55:30 + Ready + IP address: 10.11.0.193 + + From 1f7a15c534ff1628e23c6abc0b1b7bb9633dffdf Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Sun, 19 Apr 2020 22:14:16 +0200 Subject: [PATCH 05/24] Fix for GH markup --- tools/digital-signing.md | 282 +++++++++++++++++++-------------------- 1 file changed, 141 insertions(+), 141 deletions(-) diff --git a/tools/digital-signing.md b/tools/digital-signing.md index 35d8081d749..2b3dbd1ae8c 100644 --- a/tools/digital-signing.md +++ b/tools/digital-signing.md @@ -59,8 +59,8 @@ role of that interop.redwax.eu server would be a service in your own environment (we'll get to why that is a good idea later). So to install you run: - ./espota.py -i -f .../SecureOTA.bin \ - --sign=https://interop.redwax.eu/test/timestamp + ./espota.py -i -f .../SecureOTA.bin \ + --sign=https://interop.redwax.eu/test/timestamp while you watch the serial output. @@ -71,7 +71,7 @@ takethe example 'SecureOTA' and open it. The next step would be to generate a public/private key; by doing - ./espota.py --signing-key=secret-signing-key.pem --generate-signing-key + ./espota.py --signing-key=secret-signing-key.pem --generate-signing-key this will write out the secret file to disk; but also show you the public (i.e. the non secret key) in an easy to include in @@ -80,7 +80,7 @@ paste it into the SecureOTA.ino example. Now change or add a line that says: - signatureChecker.addTrustedCertAsDER(signing_cert, sizeof(signing_cert)); + signatureChecker.addTrustedCertAsDER(signing_cert, sizeof(signing_cert)); If you leave in the existing line - then the Uploader will accept both your key and RedWax signed (insecure ones). Now tranfer this by serial or @@ -90,12 +90,12 @@ current firmware will still accept RedWax signed uploads. Once it is in - you can use your own key (in the file - secret-signing-key.pem + secret-signing-key.pem ) to sign and upload: - ./espota.py -i -f .../SecureOTA.bin \ - --signing-key=secret-signing-key.pem + ./espota.py -i -f .../SecureOTA.bin \ + --signing-key=secret-signing-key.pem ## C) So what happens if I loose that private key. @@ -120,20 +120,20 @@ That is indeed the case. So this is what PKI has been invented for. What you do here is that you use a hierarchy of signatures. So suppose you have a hierarchy of: - myCorp Certifcate Authority - | - +---- production - | | - | +--- product 1 - | - +---- engineering - | - +---- development - | | - | +- Fred - | +- Mary - | - +---- pilot-testing + myCorp Certifcate Authority + | + +---- production + | | + | +--- product 1 + | + +---- engineering + | + +---- development + | | + | +- Fred + | +- Mary + | + +---- pilot-testing Where myCorp has signed production and engineering. And production has signed plant . With engineering having signed development and pilot @@ -166,134 +166,134 @@ With logging set to 'verbose 1) normal boot - rst:0xc (SW_CPU_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT) - configsip: 0, SPIWP:0xee - clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 - mode:DIO, clock div:1 - load:0x3fff0018,len:4 - load:0x3fff001c,len:1044 - load:0x40078000,len:8896 - load:0x40080400,len:5816 - entry 0x400806ac - Booting Apr 19 2020 21:55:30 - [D][WiFiGeneric.cpp:337] _eventCallback(): Event: 0 - WIFI_READY - [D][WiFiGeneric.cpp:337] _eventCallback(): Event: 2 - STA_START - [D][WiFiGeneric.cpp:337] _eventCallback(): Event: 4 - STA_CONNECTED - [D][WiFiGeneric.cpp:337] _eventCallback(): Event: 7 - STA_GOT_IP - [D][WiFiGeneric.cpp:381] _eventCallback(): STA IP: 10.11.0.193, MASK: 255.255.255.0, GW: 10.11.0.1 - [I][SecureArduinoOTA.cpp:153] begin(): OTA server at: ota-test.local:3232 - Ready - IP address: 10.11.0.193 - + rst:0xc (SW_CPU_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT) + configsip: 0, SPIWP:0xee + clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 + mode:DIO, clock div:1 + load:0x3fff0018,len:4 + load:0x3fff001c,len:1044 + load:0x40078000,len:8896 + load:0x40080400,len:5816 + entry 0x400806ac + Booting Apr 19 2020 21:55:30 + [D][WiFiGeneric.cpp:337] _eventCallback(): Event: 0 - WIFI_READY + [D][WiFiGeneric.cpp:337] _eventCallback(): Event: 2 - STA_START + [D][WiFiGeneric.cpp:337] _eventCallback(): Event: 4 - STA_CONNECTED + [D][WiFiGeneric.cpp:337] _eventCallback(): Event: 7 - STA_GOT_IP + [D][WiFiGeneric.cpp:381] _eventCallback(): STA IP: 10.11.0.193, MASK: 255.255.255.0, GW: 10.11.0.1 + [I][SecureArduinoOTA.cpp:153] begin(): OTA server at: ota-test.local:3232 + Ready + IP address: 10.11.0.193 + 2) ESP32 up - inbound oTA rquest - [I][SecureArduinoOTA.cpp:292] _onRxHeaderPhase(): OTA Outer Digest: MD5: D28374F44E8B358E189FAF4FB6AFB8FF - [D][SecureUpdater.cpp:169] begin(): OTA Partition: app1 - Updating sketch - + [I][SecureArduinoOTA.cpp:292] _onRxHeaderPhase(): OTA Outer Digest: MD5: D28374F44E8B358E189FAF4FB6AFB8FF + [D][SecureUpdater.cpp:169] begin(): OTA Partition: app1 + Updating sketch + 3) Start of signature validation - [D][SecureUpdateProcessors.cpp:202] process_header(): RFC 3161 signed payload - [D][SecureUpdateProcessors.cpp:231] process_header(): Processing RFC 3161 signed payload + [D][SecureUpdateProcessors.cpp:202] process_header(): RFC 3161 signed payload + [D][SecureUpdateProcessors.cpp:231] process_header(): Processing RFC 3161 signed payload + + PKIStatus: 0 + idSig: 9 1.2.840.113549.1.7.2 + SignedData Version 3 + SignedData DigestAlgorithmIdentifier:algoritm OID: 2.16.840.1.101.3.4.2.1 + MD algoritm internal num: 6 SHA256 + SignedData DigestAlgorithmIdentifier: 6eContentType: 11 1.2.840.113549.1.9.16.1.4 + TSTInfo Version 1 + algoritm OID: 2.16.840.1.101.3.4.2.1 + MD algoritm internal num: 6 SHA256 + The main hash of the payload: len=32: f8baadde2339ebbed...6c31e743496043d33f1f. + Serial 16883855617417564721 + Signature timestap: 2020-04-19 19:57:58 UTC + Accuracy 1.0.0 (ignored 0 bytes) + Extracted 1 certs + No CRLs + SID name CN=Redwax Interop Testing Root Certificate Authority 2040, O=Redwax Project + DigestAlgorithmIdentifier:algoritm OID: 2.16.840.1.101.3.4.2.1 + MD algoritm internal num: 6 SHA256 + Signed attributes: + 1.2.840.113549.1.9.3: + Not decoded -- skipped + 1.2.840.113549.1.9.5: + Not decoded -- skipped + 1.2.840.113549.1.9.16.2.12: + Signing cert hash V1 (4): ff4237eaedc05da815c24db853f0d2bfda34da5c. + 1.2.840.113549.1.9.4: + Digest (signed section): len=32: 7bc441fcc129d9a4a90e5ed....784fb4c59f4d2216af. + SignatureAlgorithmIdentifier:algoritm OID: 1.2.840.113549.1.1.1 + PK algoritm internal num: 1 + Signature (512 bytes): 075fc499f569997fbea2d66e7d8c6cdae8....a8912bb3f54ee91f01df8b. + 0 hash: ff4237eaedc05da815c24db853f0d2bfda34da5c. + Found one that matched + Indirected signing. + Attribute Signed Data (s=1629, l=155) -- calculated digest: len=32: 69ef99b2713c41e6...099aeded0c45b6bedb. + Signature on the Reply OK - PKIStatus: 0 - idSig: 9 1.2.840.113549.1.7.2 - SignedData Version 3 - SignedData DigestAlgorithmIdentifier:algoritm OID: 2.16.840.1.101.3.4.2.1 - MD algoritm internal num: 6 SHA256 - SignedData DigestAlgorithmIdentifier: 6eContentType: 11 1.2.840.113549.1.9.16.1.4 - TSTInfo Version 1 - algoritm OID: 2.16.840.1.101.3.4.2.1 - MD algoritm internal num: 6 SHA256 - The main hash of the payload: len=32: f8baadde2339ebbed...6c31e743496043d33f1f. - Serial 16883855617417564721 - Signature timestap: 2020-04-19 19:57:58 UTC - Accuracy 1.0.0 (ignored 0 bytes) - Extracted 1 certs - No CRLs - SID name CN=Redwax Interop Testing Root Certificate Authority 2040, O=Redwax Project - DigestAlgorithmIdentifier:algoritm OID: 2.16.840.1.101.3.4.2.1 - MD algoritm internal num: 6 SHA256 - Signed attributes: - 1.2.840.113549.1.9.3: - Not decoded -- skipped - 1.2.840.113549.1.9.5: - Not decoded -- skipped - 1.2.840.113549.1.9.16.2.12: - Signing cert hash V1 (4): ff4237eaedc05da815c24db853f0d2bfda34da5c. - 1.2.840.113549.1.9.4: - Digest (signed section): len=32: 7bc441fcc129d9a4a90e5ed....784fb4c59f4d2216af. - SignatureAlgorithmIdentifier:algoritm OID: 1.2.840.113549.1.1.1 - PK algoritm internal num: 1 - Signature (512 bytes): 075fc499f569997fbea2d66e7d8c6cdae8....a8912bb3f54ee91f01df8b. - 0 hash: ff4237eaedc05da815c24db853f0d2bfda34da5c. - Found one that matched - Indirected signing. - Attribute Signed Data (s=1629, l=155) -- calculated digest: len=32: 69ef99b2713c41e6...099aeded0c45b6bedb. - Signature on the Reply OK - - - - - 4) Processing of the signature - [D][SecureUpdateProcessors.cpp:246] process_header(): Processed RFC 3161 signed payload - [I][SecureUpdateProcessors.cpp:254] process_header(): Processing plaintext with SHA256 specified RFC3161 digest. - [D][SecureUpdateProcessors.cpp:265] process_header(): RFC3161 signature verified. - [D][SecureUpdateProcessors.cpp:274] process_header(): Signatures in the trust chain: - [D][SecureUpdateProcessors.cpp:278] process_header(): - cert. version : 3 - - serial number : 6F:11:B7:D8:55:D2:7D:9A:14:F3:B6:E9:15:2B:60:CA:8C:4B:E2:AA - - issuer name : CN=Redwax Interop Testing Root Certificate Authority 2040, O=Redwax Project - - subject name : CN=Redwax Interop Testing Root Certificate Authority 2040, O=Redwax Project - - issued on : 2020-02-11 16:38:56 - - expires on : 2040-02-06 16:38:56 - - signed using : RSA with SHA1 - - RSA key size : 2048 bits - - basic constraints : CA=true - [D][SecureUpdateProcessors.cpp:281] process_header(): Signatures in the RFC3161 wrapper: - [D][SecureUpdateProcessors.cpp:285] process_header(): - cert. version : 3 - - serial number : 05 - - issuer name : CN=Redwax Interop Testing Root Certificate Authority 2040, O=Redwax Project - - subject name : C=NL, ST=Zuid-Holland, L=Leiden, O=TimeServices, CN=Redwax Interop Test - - issued on : 2020-02-15 20:51:52 - - expires on : 2040-02-10 20:51:52 - - signed using : RSA with SHA-256 - - RSA key size : 4096 bits - - basic constraints : CA=false - - key usage : Digital Signature - - ext key usage : Time Stamping - - [I][SecureUpdateProcessors.cpp:293] process_header(): RFC3161 signature on timestamp and payload digest verified. - [D][SecureUpdateProcessors.cpp:303] process_header(): RFC3161: processing payload. - [D][SecureUpdateProcessors.cpp:51] process_header(): Valid magic at start of flash header - [D][SecureUpdateProcessors.cpp:362] process_end(): RFC3161 Finalizing payload digest - [D][SecureUpdateProcessors.cpp:382] process_end(): Payload calculated SHA256 Digest 32:f8baadde2339ebbed1b3afad3d6a9ad3cee69555f6c31e743496043d33f1f - [D][SecureUpdateProcessors.cpp:387] process_end(): RFC3161 Receveived SHA256 Digest 32:f8baadde2339ebbed1b3afad3d6a9ad3cee69555f6c31e743496043d33f1f - [I][SecureUpdateProcessors.cpp:402] process_end(): RFC3161 Payload digest matches signed digest. - [D][SecureUpdater.cpp:400] end(): Reporting an OK back up the chain - [D][SecureArduinoOTA.cpp:561] _runUpdate(): OTA Outer Digest matched. - [D][SecureUpdateProcessors.cpp:131] reset(): RESET - [D][SecureUpdater.cpp:359] activate(): RAW SHA256 Digest 361ab6322fa9e7a7bb23818d839e1bddafdf4735426edd297aedb9f6202bae + + + 4) Processing of the signature + [D][SecureUpdateProcessors.cpp:246] process_header(): Processed RFC 3161 signed payload + [I][SecureUpdateProcessors.cpp:254] process_header(): Processing plaintext with SHA256 specified RFC3161 digest. + [D][SecureUpdateProcessors.cpp:265] process_header(): RFC3161 signature verified. + [D][SecureUpdateProcessors.cpp:274] process_header(): Signatures in the trust chain: + [D][SecureUpdateProcessors.cpp:278] process_header(): - cert. version : 3 + - serial number : 6F:11:B7:D8:55:D2:7D:9A:14:F3:B6:E9:15:2B:60:CA:8C:4B:E2:AA + - issuer name : CN=Redwax Interop Testing Root Certificate Authority 2040, O=Redwax Project + - subject name : CN=Redwax Interop Testing Root Certificate Authority 2040, O=Redwax Project + - issued on : 2020-02-11 16:38:56 + - expires on : 2040-02-06 16:38:56 + - signed using : RSA with SHA1 + - RSA key size : 2048 bits + - basic constraints : CA=true + + [D][SecureUpdateProcessors.cpp:281] process_header(): Signatures in the RFC3161 wrapper: + [D][SecureUpdateProcessors.cpp:285] process_header(): - cert. version : 3 + - serial number : 05 + - issuer name : CN=Redwax Interop Testing Root Certificate Authority 2040, O=Redwax Project + - subject name : C=NL, ST=Zuid-Holland, L=Leiden, O=TimeServices, CN=Redwax Interop Test + - issued on : 2020-02-15 20:51:52 + - expires on : 2040-02-10 20:51:52 + - signed using : RSA with SHA-256 + - RSA key size : 4096 bits + - basic constraints : CA=false + - key usage : Digital Signature + - ext key usage : Time Stamping + + [I][SecureUpdateProcessors.cpp:293] process_header(): RFC3161 signature on timestamp and payload digest verified. + [D][SecureUpdateProcessors.cpp:303] process_header(): RFC3161: processing payload. + [D][SecureUpdateProcessors.cpp:51] process_header(): Valid magic at start of flash header + [D][SecureUpdateProcessors.cpp:362] process_end(): RFC3161 Finalizing payload digest + [D][SecureUpdateProcessors.cpp:382] process_end(): Payload calculated SHA256 Digest 32:f8baadde2339ebbed1b3afad3d6a9ad3cee69555f6c31e743496043d33f1f + [D][SecureUpdateProcessors.cpp:387] process_end(): RFC3161 Receveived SHA256 Digest 32:f8baadde2339ebbed1b3afad3d6a9ad3cee69555f6c31e743496043d33f1f + [I][SecureUpdateProcessors.cpp:402] process_end(): RFC3161 Payload digest matches signed digest. + [D][SecureUpdater.cpp:400] end(): Reporting an OK back up the chain + [D][SecureArduinoOTA.cpp:561] _runUpdate(): OTA Outer Digest matched. + [D][SecureUpdateProcessors.cpp:131] reset(): RESET + [D][SecureUpdater.cpp:359] activate(): RAW SHA256 Digest 361ab6322fa9e7a7bb23818d839e1bddafdf4735426edd297aedb9f6202bae 5) Final phase - ESP 32 partition activated and rebooted. - [D][SecureUpdater.cpp:211] abort(): Aborted. - [D][SecureUpdateProcessors.cpp:131] reset(): RESET - [D][WiFiGeneric.cpp:337] _eventCallback(): Event: 3 - STA_STOP - [D][WiFiGeneric.cpp:337] _eventCallback(): Event: 3 - STA_STOP - ets Jun 8 2016 00:22:57 - - rst:0xc (SW_CPU_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT) - configsip: 0, SPIWP:0xee - clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 - mode:DIO, clock div:1 - load:0x3fff0018,len:4 - load:0x3fff001c,len:1044 - load:0x40078000,len:8896 - load:0x40080400,len:5816 - entry 0x400806ac - Booting Apr 19 2020 21:55:30 - Ready - IP address: 10.11.0.193 + [D][SecureUpdater.cpp:211] abort(): Aborted. + [D][SecureUpdateProcessors.cpp:131] reset(): RESET + [D][WiFiGeneric.cpp:337] _eventCallback(): Event: 3 - STA_STOP + [D][WiFiGeneric.cpp:337] _eventCallback(): Event: 3 - STA_STOP + ets Jun 8 2016 00:22:57 + + rst:0xc (SW_CPU_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT) + configsip: 0, SPIWP:0xee + clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 + mode:DIO, clock div:1 + load:0x3fff0018,len:4 + load:0x3fff001c,len:1044 + load:0x40078000,len:8896 + load:0x40080400,len:5816 + entry 0x400806ac + Booting Apr 19 2020 21:55:30 + Ready + IP address: 10.11.0.193 From 33ad4e3c931637e480ad7cc2967418763c95d382 Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Sun, 19 Apr 2020 22:15:11 +0200 Subject: [PATCH 06/24] Fix for GH markup 2 --- tools/digital-signing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/digital-signing.md b/tools/digital-signing.md index 2b3dbd1ae8c..12f1d827337 100644 --- a/tools/digital-signing.md +++ b/tools/digital-signing.md @@ -233,9 +233,9 @@ With logging set to 'verbose Signature on the Reply OK - - 4) Processing of the signature +4) Processing of the signature + [D][SecureUpdateProcessors.cpp:246] process_header(): Processed RFC 3161 signed payload [I][SecureUpdateProcessors.cpp:254] process_header(): Processing plaintext with SHA256 specified RFC3161 digest. [D][SecureUpdateProcessors.cpp:265] process_header(): RFC3161 signature verified. From bb159943ff9d67261a1e547a8bcde56c27abbe6e Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Sun, 19 Apr 2020 22:17:40 +0200 Subject: [PATCH 07/24] Fix for case sensitive platforms --- libraries/Update/src/UpdateProcessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/Update/src/UpdateProcessor.cpp b/libraries/Update/src/UpdateProcessor.cpp index e57a42c7a8e..7ffab100f33 100644 --- a/libraries/Update/src/UpdateProcessor.cpp +++ b/libraries/Update/src/UpdateProcessor.cpp @@ -6,7 +6,7 @@ #include "esp_partition.h" #include "esp_spi_flash.h" -#include "Updateprocessor.h" +#include "UpdateProcessor.h" /* We have four formats; this handler deals with the original format: From e7d35e85a7d969d9f2206b436866d270e12c09e9 Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Mon, 20 Apr 2020 11:22:46 +0200 Subject: [PATCH 08/24] Make it easier to cut-and-paste key. --- tools/espota.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/espota.py b/tools/espota.py index 9faf479bb14..b2a803db2f2 100755 --- a/tools/espota.py +++ b/tools/espota.py @@ -528,11 +528,17 @@ def main(args): cmd = "openssl x509 -in '{}' -outform DER -out '{}'".format(options.tskey, tmpn) os.system(cmd) - print("const unsigned char signing_cert[] = { ") with open(tmpn,'rb') as f: keybytes = f.read() - print(','.join('0x{:02x}'.format(ord(b)) for b in keybytes)) + print("/* DER encoded signing certificate with (just the) public key */") + print("#define signing_cert_len ((size_t){})".format(len(keybytes))) + print("const unsigned char signing_cert[{}] = {{ ".format(len(keybytes))) + for chunk in [keybytes[i:i+16] for i in range(0, len(keybytes), 16)]: + l = ',' + if len(chunk) != 16: + l = '' + print('\t{}{}'.format(','.join('0x{:02x}'.format(ord(b)) for b in chunk),l)) print("}; // end of signing cert") os.unlink(tmpn) From 00a578ef053b8396dbbe99a318351a3a79f2aa98 Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Mon, 20 Apr 2020 11:23:13 +0200 Subject: [PATCH 09/24] Trigger the watchdog at times - to not get hit by it during a network stall. --- tools/x | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 tools/x diff --git a/tools/x b/tools/x new file mode 100644 index 00000000000..78572e0931d --- /dev/null +++ b/tools/x @@ -0,0 +1,47 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC+Frs/k3M/Szbs +XCBtEOoOPKxA1zQ4/UUoliiIbPlrr2Bgg6CAHYV83BRjjse5jy3+fGVwspQwu04Y +Z5KBVQgm2mvGppzBMolTa/9xc2Xk7BXxSv7NnouUqao/UDrctn46q/dzrkYLNoPw +Hrrf6COwhQow4ZNSwozkSNVCBjaCcRNU72JnG9Sirs3Qk1JMfe3/6V82VoU9t6qZ +DnEYe9oJ41zzyITNN69hkSLdGp1gT2S2Wqr/Fas0CSzSwW7V4yewM3iRztEpirG3 +V3Kq/hnmlK6yBWstEja3GUEITZ9RFo4MQE18Kq0fN2OR/gduwx1qp1t9+a6R0ICx +F8eKSouBAgMBAAECggEAan55sQh+jHsq7gg+8luDhS5gooLucu8Ri8d0Ndo3cijy +qZ+Uj7H6UxVJJu2a305jyisO+bBSbWrCOdHstiBZTMsZjlVRhLs3TFE47upjr3jE +YkZNsgyczlCvXIEqOPTUizJEk76S5z1HdYMB1udK913Rc8ksrDTqkgprz22DpFeV +QrxEUZHaXF8QkAsmQK8IvUh36cQ3rljEt61W1lTn6EgiKEBdjzlS5T8ByX/s9afH +MPA8FWvi7pF1v+A8xF3duEUFwlu8MQSeLpdCfnbgmgh9xAIAcveuq/ygbeTXcjG3 +X2k2IA5QjaGZLPFOLANSsLdatpWR0EiTUY3tstQQAQKBgQDuJuvlwXJnnmDVSreZ +LG6w+Usn8Ll+oCkVogGhuEOS7wJDmlLgjyortkg7hiNpH/S8D/EQrTcK1OBQ1f6M +jndCqRwiI72bfI6RmOtFUuFgGKVv1PM598ny91ooVbPnFLlABaPY4zxGz7629b2r +4Xm4Y7XVWBivomfAm1JBHoHr4QKBgQDMVbCpM760yh3avitxpRzPjm7EYON+Pp37 +ypt5+tCDJLI6LEnXj/kHSnA1AlYRYNAKdFOXNmYANi/UXTfSNdUi0QWOxYTg6dzP +lCm8dlzvPB6MSDJ8QLAyxDa2c8/RAjTMRlzwlhx/RarFxu7s/Nz35O587krrF7iZ ++QlUjIOToQKBgEmPZiACn6bX6csYGBvM+KfbaNZ+aZ1uNSKEdwKP2veAse9VtLG+ +JuV3mVohdcCb6UlJl0lyZ2UGGf9+CfE6dCX3/EOjTIbBc3wPguRX1FuNYVRor3RB +UHSm6Ic66aW/5fSbiV+N2Ol6c8spnpt/usw0qE6stntRq8B3eXG5zaGhAoGBAI8J +qTo4+9Xo6s82cokCcVGmPkoAHSBByyv4n6/a8N3s8UqhuCtLIKOG61dgREm0AM5g +sidXMD4TsJWzj6D6iDHwKjjQcf1UGHvDFxhTryjVZ/kBjT3HttBC5J4CIkcIiVc6 +JlNQcbByX2JFqPmC5VgvDPethApgN5ypZWunVXAhAoGAGVrAHCAbAq36p50q9Hlm +sqiincoZOjwUsZ8yHYheGnGG12vu3SMh/erURnIrblDkv29X3VjoRxY740MikfzF +n3QTw+DAXjG2s5sqlCAPIb/BYbGgXD8fwcK6zKPizlEVEXWR3gZxci1hToVoNnT0 +4leVaukit+L19bgJkaTF3Gg= +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDIzCCAgugAwIBAgIUKhLblYDQkAn8rjxdX3krTB5/liQwDQYJKoZIhvcNAQEL +BQAwFTETMBEGA1UEAwwKQXJkdWlub09UQTAeFw0yMDA0MjAwOTIyMjBaFw0yMDA1 +MjAwOTIyMjBaMBUxEzARBgNVBAMMCkFyZHVpbm9PVEEwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQC+Frs/k3M/SzbsXCBtEOoOPKxA1zQ4/UUoliiIbPlr +r2Bgg6CAHYV83BRjjse5jy3+fGVwspQwu04YZ5KBVQgm2mvGppzBMolTa/9xc2Xk +7BXxSv7NnouUqao/UDrctn46q/dzrkYLNoPwHrrf6COwhQow4ZNSwozkSNVCBjaC +cRNU72JnG9Sirs3Qk1JMfe3/6V82VoU9t6qZDnEYe9oJ41zzyITNN69hkSLdGp1g +T2S2Wqr/Fas0CSzSwW7V4yewM3iRztEpirG3V3Kq/hnmlK6yBWstEja3GUEITZ9R +Fo4MQE18Kq0fN2OR/gduwx1qp1t9+a6R0ICxF8eKSouBAgMBAAGjazBpMB0GA1Ud +DgQWBBRiTts814ii31hX1R1uIYb8iM4llzAfBgNVHSMEGDAWgBRiTts814ii31hX +1R1uIYb8iM4llzAPBgNVHRMBAf8EBTADAQH/MBYGA1UdJQEB/wQMMAoGCCsGAQUF +BwMIMA0GCSqGSIb3DQEBCwUAA4IBAQBfw6lpJyoPVMt2pJ/dXQr9ehvYoIRnsjRd +VckcMifoSbQloxOoxPRY5dOFoELdVeV+dK3kUZGiKTUOHuogIsMTZ28XfSw4Xsrz +efEwpRrSThf3DUVNsP/uH5BpSRaGT72rMFm8G9i19TUpf0wY58OvlNumHwBWaDa3 +4tKpePYAxzJyWLIm/KmSfMxKWldIMHVLXtkRLON1K2HIh32ikN39KeciDLs8Uh9i +Ufueb28/2u7iTCLfsOuSG/E5AKyrkNwW8wcjP8vEIpek1J+OORfZ5Dz5Mkwqt73b +7pRMn+ybGNU4RA8vrAtVk6zfoW5uUP+ZXxRu7vsmCwuzyNDn1+Ca +-----END CERTIFICATE----- From 36731f3c5c237e6afdae70fd03695b43b91c9057 Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Mon, 20 Apr 2020 11:54:10 +0200 Subject: [PATCH 10/24] Changes for PIO --- CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index d38f9d43098..6ab0094b94b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,11 @@ set(LIBRARY_SRCS libraries/SPI/src/SPI.cpp libraries/Ticker/src/Ticker.cpp libraries/Update/src/Updater.cpp + libraries/Update/src/UpdateProcessor.cpp + libraries/Update/src/UpdateProcessorRFC3161.cpp + libraries/Update/src/mbedtls-ts-addons/signer_info.cpp + libraries/Update/src/mbedtls-ts-addons/x509_ts_utils.cpp + libraries/Update/src/mbedtls-ts-addons/ts.cpp libraries/WebServer/src/WebServer.cpp libraries/WebServer/src/Parsing.cpp libraries/WebServer/src/detail/mimetable.cpp @@ -198,6 +203,7 @@ set(COMPONENT_ADD_INCLUDEDIRS libraries/SPI/src libraries/Ticker/src libraries/Update/src + libraries/Update/src/mbedtls-ts-addons libraries/WebServer/src libraries/WiFiClientSecure/src libraries/WiFi/src From 0e235ff1f6f8787b280a83215a07b2086b8c3daf Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Mon, 20 Jul 2020 21:26:36 +0200 Subject: [PATCH 11/24] Few tiny tweaks; add more debugging output and a more sensible error (Credits to @noisegate for finding it). --- libraries/ArduinoOTA/src/ArduinoOTA.cpp | 2 + libraries/HTTPUpdate/src/HTTPUpdate.h | 5 ++ .../Update/src/UpdateProcessorRFC3161.cpp | 11 +++- .../src/mbedtls-ts-addons/x509_crt_utils.cpp | 60 +++++++++++++++++++ .../src/mbedtls-ts-addons/x509_crt_utils.h | 14 +++++ 5 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.cpp create mode 100644 libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.h diff --git a/libraries/ArduinoOTA/src/ArduinoOTA.cpp b/libraries/ArduinoOTA/src/ArduinoOTA.cpp index ca890543460..280d3ada89d 100644 --- a/libraries/ArduinoOTA/src/ArduinoOTA.cpp +++ b/libraries/ArduinoOTA/src/ArduinoOTA.cpp @@ -496,6 +496,8 @@ char * ArduinoOTAClass::_handleUpdate(WiFiClient & client) { if (!client.printf("%u\n", written)) return (char *)"failed to return bytes written"; + + delay(0); // see PR#3910 and issue i#3528, #3775, etc } // while we're connected and there is data available. return NULL; } diff --git a/libraries/HTTPUpdate/src/HTTPUpdate.h b/libraries/HTTPUpdate/src/HTTPUpdate.h index f126cba0639..e58ca837aa0 100644 --- a/libraries/HTTPUpdate/src/HTTPUpdate.h +++ b/libraries/HTTPUpdate/src/HTTPUpdate.h @@ -33,6 +33,8 @@ #include #include +#include + /// note we use HTTP client errors too so we start at 100 #define HTTP_UE_TOO_LESS_SPACE (-100) #define HTTP_UE_SERVER_NOT_REPORT_SIZE (-101) @@ -92,6 +94,9 @@ class HTTPUpdate int _ledPin; uint8_t _ledOn; + + const mbedtls_md_info_t *_md_info; + mbedtls_md_context_t * _md_ctx; }; #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_HTTPUPDATE) diff --git a/libraries/Update/src/UpdateProcessorRFC3161.cpp b/libraries/Update/src/UpdateProcessorRFC3161.cpp index caed3c61ac1..ada641d4466 100644 --- a/libraries/Update/src/UpdateProcessorRFC3161.cpp +++ b/libraries/Update/src/UpdateProcessorRFC3161.cpp @@ -6,6 +6,8 @@ #include "esp_partition.h" #include "esp_spi_flash.h" +#include "mbedtls-ts-addons/x509_crt_utils.h" + #include "UpdateProcessorRFC3161.h" /* We have three formats; @@ -125,6 +127,7 @@ void UpdateProcessorRFC3161::reset() { _state = INIT; }; + UpdateProcessor::secure_update_processor_err_t UpdateProcessorRFC3161::process_header(uint32_t *command, uint8_t * buffer, size_t *len) { unsigned char * p; uint32_t results = 0; @@ -255,6 +258,8 @@ UpdateProcessor::secure_update_processor_err_t UpdateProcessorRFC3161::process_h char buf[1024 * 2]; mbedtls_x509_crt_info(buf, sizeof(buf), " - ", c); log_d(" %s", buf); + mbedtls_x509_crt_fprint(buff, sizeof(buff)," - ", c, MBEDTLS_MD_SHA256); + log_d(" %s", buf); }; log_d("Signatures in the RFC3161 wrapper:"); @@ -262,11 +267,15 @@ UpdateProcessor::secure_update_processor_err_t UpdateProcessorRFC3161::process_h char buf[1024 * 2]; mbedtls_x509_crt_info(buf, sizeof(buf), " - ", c); log_d(" %s", buf); + mbedtls_x509_crt_fprint(buff, sizeof(buff)," - ", c, MBEDTLS_MD_SHA256); + log_d(" %s", buf); }; } #endif if (mbedtls_x509_crt_verify(_reply.chain, _trustChain, NULL /* no CRL */, NULL /* any name fine */, &results, NULL, NULL) != 0) { - log_e("RFC3161 signature could not be validated against the chain. Aborting."); + char err_mess[128]; + mbedtls_x509_crt_verify_info(err_mess, sizeof(err_mess),"",results); + log_e("RFC3161 signature could not be validated against the chain: %s. Aborting.", err_mess); return secure_update_processor_ERROR; } log_i("RFC3161 signature on timestamp and payload digest verified."); diff --git a/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.cpp b/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.cpp new file mode 100644 index 00000000000..4395664d865 --- /dev/null +++ b/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.cpp @@ -0,0 +1,60 @@ +#include +#include "x509_crt_utils.h" + +int mbedtls_x509_crt_fprint(char * buff, size_t len, char * prefix, mbedtls_x509_crt * cert, mbedtls_md_type_t tpe) +{ + const mbedtls_md_info_t * mdt = mbedtls_md_info_from_type(tpe ? tpe : MBEDTLS_MD_SHA256); + unsigned char *output = NULL; + char *p = buff, * ep = buff + len-4; + mbedtls_md_context_t *ctx; + int i, ret = -1; + size_t l; + + mbedtls_md_init(&ctx); + if ( + ((output = mbedtls_malloc(mbedtls_md_get_size(mdt))) != NULL) || + ((ret = mbedtls_md_setup(&ctx, mdt, 9)) != 0) || + ((ret = mbedtls_md_starts (&ctx)) != 0) || + ((ret = mbedtls_md_update (&ctxm, cert->raw.p, cert->raw.len)) != 0) || + ((ret = mbedtls_md_finish (&ctxm, output)) != 0) + ) goto erx; + + p = buff; + if (prefix) { + l = MIN(strlen(prefix), ep-p); + memcpy(p, prefix, l); + p += l; + }; + + const char * nme = mbedtls_md_get_name(mdt); + l = MIN(strlen(nme), ep-p); + memcpy(p, nme, l); + p += l; + + const char * txt = " fingerprint: "; + l = MIN(strlen(txt), ep-p); + memcpy(p, txt, l); + p += l; + + for(i = 0; (i < 32); i++) { + int c1 = output[i] >> 4; + int c2 = output[i] & 0xF; + if (p < ep) + *(p++) = (c1 < 10) ? '0' + c1 : 'A' + (c1 - 10); + if (p < ep) + *(p++) = (c2 < 10) ? '0' + c2 : 'A' + (c2 - 10); + }; + + // Add 3 ominous dots, like ellipses, if our buffer falls short. + if (p == ep) { + *(p++)='.';*(p++)='.';*(p++)='.'; + }; + *(p++)= '\0'; + ret = 0; + +erx: + mbedtls_md_free(&ctx); + if (output) mbedtls_free(output); + return ret; +} + diff --git a/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.h b/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.h new file mode 100644 index 00000000000..d42b07cfea3 --- /dev/null +++ b/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.h @@ -0,0 +1,14 @@ +#include "mbedtls/config.h" +#include "mbedtls/error.h" +#include "mbedtls/x509.h" +#include "mbedtls/x509_crt.h" +#include +#include +#include + +#ifndef _H_X509_CRT_UTILS +#define _H_X509_CRT_UTILS + +int mbedtls_x509_crt_fprint(char * buff, size_t len, char * prefixOrNull, mbedtls_x509_crt * cert, mbedtls_md_type_t typeOrNull); +#endif + From 0e1cbd6a423d0388e3bb0d1db2ca0fb707153ac8 Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Mon, 20 Jul 2020 21:52:59 +0200 Subject: [PATCH 12/24] Clean up compiler warnings --- .../src/mbedtls-ts-addons/x509_crt_utils.cpp | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.cpp b/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.cpp index 4395664d865..4dffb66569c 100644 --- a/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.cpp +++ b/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.cpp @@ -1,22 +1,33 @@ +#include #include + +#include "mbedtls/platform.h" + #include "x509_crt_utils.h" +#ifndef MIN +#define MIN(a,b) ( ((a) <(b)) ? (a) : (b)) +#endif + int mbedtls_x509_crt_fprint(char * buff, size_t len, char * prefix, mbedtls_x509_crt * cert, mbedtls_md_type_t tpe) { const mbedtls_md_info_t * mdt = mbedtls_md_info_from_type(tpe ? tpe : MBEDTLS_MD_SHA256); + const char * nme = mbedtls_md_get_name(mdt); + const char * txt = " fingerprint: "; + unsigned char *output = NULL; char *p = buff, * ep = buff + len-4; - mbedtls_md_context_t *ctx; + mbedtls_md_context_t ctx; int i, ret = -1; size_t l; mbedtls_md_init(&ctx); if ( - ((output = mbedtls_malloc(mbedtls_md_get_size(mdt))) != NULL) || + ((output = (unsigned char *)mbedtls_calloc(mbedtls_md_get_size(mdt),1)) != NULL) || ((ret = mbedtls_md_setup(&ctx, mdt, 9)) != 0) || ((ret = mbedtls_md_starts (&ctx)) != 0) || - ((ret = mbedtls_md_update (&ctxm, cert->raw.p, cert->raw.len)) != 0) || - ((ret = mbedtls_md_finish (&ctxm, output)) != 0) + ((ret = mbedtls_md_update (&ctx, cert->raw.p, cert->raw.len)) != 0) || + ((ret = mbedtls_md_finish (&ctx, output)) != 0) ) goto erx; p = buff; @@ -26,12 +37,10 @@ int mbedtls_x509_crt_fprint(char * buff, size_t len, char * prefix, mbedtls_x509 p += l; }; - const char * nme = mbedtls_md_get_name(mdt); l = MIN(strlen(nme), ep-p); memcpy(p, nme, l); p += l; - const char * txt = " fingerprint: "; l = MIN(strlen(txt), ep-p); memcpy(p, txt, l); p += l; From be7122c5eb876459d7cf732a3c56429567b4a0c1 Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Mon, 20 Jul 2020 22:14:48 +0200 Subject: [PATCH 13/24] Fix return values to be in line with other _info calls, follow OpenSSL output standard, add API docs. --- .../src/mbedtls-ts-addons/x509_crt_utils.cpp | 14 ++++++++++---- .../src/mbedtls-ts-addons/x509_crt_utils.h | 17 ++++++++++++++++- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.cpp b/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.cpp index 4dffb66569c..26ad6795764 100644 --- a/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.cpp +++ b/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.cpp @@ -13,7 +13,8 @@ int mbedtls_x509_crt_fprint(char * buff, size_t len, char * prefix, mbedtls_x509 { const mbedtls_md_info_t * mdt = mbedtls_md_info_from_type(tpe ? tpe : MBEDTLS_MD_SHA256); const char * nme = mbedtls_md_get_name(mdt); - const char * txt = " fingerprint: "; + const size_t dl = mbedtls_md_get_size(mdt); + const char * txt = " Fingerprint="; unsigned char *output = NULL; char *p = buff, * ep = buff + len-4; @@ -23,7 +24,7 @@ int mbedtls_x509_crt_fprint(char * buff, size_t len, char * prefix, mbedtls_x509 mbedtls_md_init(&ctx); if ( - ((output = (unsigned char *)mbedtls_calloc(mbedtls_md_get_size(mdt),1)) != NULL) || + ((output = (unsigned char *)mbedtls_calloc(dl,1)) != NULL) || ((ret = mbedtls_md_setup(&ctx, mdt, 9)) != 0) || ((ret = mbedtls_md_starts (&ctx)) != 0) || ((ret = mbedtls_md_update (&ctx, cert->raw.p, cert->raw.len)) != 0) || @@ -45,21 +46,26 @@ int mbedtls_x509_crt_fprint(char * buff, size_t len, char * prefix, mbedtls_x509 memcpy(p, txt, l); p += l; - for(i = 0; (i < 32); i++) { + for(i = 0; i < dl; i++) { int c1 = output[i] >> 4; int c2 = output[i] & 0xF; if (p < ep) *(p++) = (c1 < 10) ? '0' + c1 : 'A' + (c1 - 10); if (p < ep) *(p++) = (c2 < 10) ? '0' + c2 : 'A' + (c2 - 10); + if (p < ep && i != dl-1) + *(p++) = ':'; }; // Add 3 ominous dots, like ellipses, if our buffer falls short. + // if (p == ep) { *(p++)='.';*(p++)='.';*(p++)='.'; }; *(p++)= '\0'; - ret = 0; + + // return the bytes written into buff. + ret = p - buff; erx: mbedtls_md_free(&ctx); diff --git a/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.h b/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.h index d42b07cfea3..50e0142d861 100644 --- a/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.h +++ b/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.h @@ -9,6 +9,21 @@ #ifndef _H_X509_CRT_UTILS #define _H_X509_CRT_UTILS -int mbedtls_x509_crt_fprint(char * buff, size_t len, char * prefixOrNull, mbedtls_x509_crt * cert, mbedtls_md_type_t typeOrNull); +/** + * \brief This function returns a buffer with the fingerprint of + * a certificate (i.e. taken of the raw DER encoded blob) + * and outputs this in the same style as OpenSSL. + * + * \param buf Buffer to write to + * \param size Maximum size of buffer + * \param prefix A line prefix + * \param crt The X509 certificate to fingerprint. + * \param mdtype The digest to use; if set to MBEDTLS_MD_NONE the default + * SHA256 will be used. + * + * \return The length of the string written (not including the + * terminated nul byte), or a negative error code. + */ +int mbedtls_x509_crt_fprint(char * buf, size_t size, char * prefix, mbedtls_x509_crt * crt, mbedtls_md_type_t mdtype); #endif From d1fbe1d357311e6eb193bf596c997d322d3d2d47 Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Mon, 20 Jul 2020 22:24:13 +0200 Subject: [PATCH 14/24] Fix CMake framework --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ab0094b94b..2dc32a7cc11 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,7 @@ set(LIBRARY_SRCS libraries/Update/src/UpdateProcessorRFC3161.cpp libraries/Update/src/mbedtls-ts-addons/signer_info.cpp libraries/Update/src/mbedtls-ts-addons/x509_ts_utils.cpp + libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.cpp libraries/Update/src/mbedtls-ts-addons/ts.cpp libraries/WebServer/src/WebServer.cpp libraries/WebServer/src/Parsing.cpp From 09fa18ba638dd1edd99752a6b18889d214b7f505 Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Tue, 21 Jul 2020 08:17:45 +0200 Subject: [PATCH 15/24] Fix/re-add MD5 checksum for plain uploads; also allow SHA256 and other digests. --- libraries/HTTPUpdate/src/HTTPUpdate.cpp | 62 ++++++------ libraries/HTTPUpdate/src/HTTPUpdate.h | 3 + libraries/Update/src/UpdateProcessor.cpp | 114 +++++++++++++++++++++++ libraries/Update/src/UpdateProcessor.h | 17 ++++ libraries/Update/src/Updater.cpp | 2 + 5 files changed, 172 insertions(+), 26 deletions(-) diff --git a/libraries/HTTPUpdate/src/HTTPUpdate.cpp b/libraries/HTTPUpdate/src/HTTPUpdate.cpp index f4c3d250fa7..d49964b71e2 100644 --- a/libraries/HTTPUpdate/src/HTTPUpdate.cpp +++ b/libraries/HTTPUpdate/src/HTTPUpdate.cpp @@ -1,6 +1,6 @@ /** * - * @file HTTPUpdate.cpp based om ESP8266HTTPUpdate.cpp + * @file HTTP_updater->cpp based om ESP8266HTTP_updater->cpp * @date 16.10.2018 * @author Markus Sattler * @@ -35,11 +35,15 @@ HTTPUpdate::HTTPUpdate(void) : _httpClientTimeout(8000), _ledPin(-1) { + _processor = new UpdateProcessorWithChecksum(); + _updater = new UpdateClass(_processor); } HTTPUpdate::HTTPUpdate(int httpClientTimeout) : _httpClientTimeout(httpClientTimeout), _ledPin(-1) { + delete _updater; + delete _processor; } HTTPUpdate::~HTTPUpdate(void) @@ -100,7 +104,7 @@ String HTTPUpdate::getLastErrorString(void) // error from Update class if(_lastError > 0) { StreamString error; - Update.printError(error); + _updater->printError(error); error.trim(); // remove line ending return String("Update error: ") + error; } @@ -122,7 +126,7 @@ String HTTPUpdate::getLastErrorString(void) case HTTP_UE_SERVER_WRONG_HTTP_CODE: return "Wrong HTTP Code"; case HTTP_UE_SERVER_FAULTY_MD5: - return "Wrong MD5"; + return "Wrong checksum"; case HTTP_UE_BIN_VERIFY_HEADER_FAILED: return "Verify Bin Header Failed"; case HTTP_UE_BIN_FOR_WRONG_FLASH: @@ -370,42 +374,48 @@ HTTPUpdateResult HTTPUpdate::handleUpdate(HTTPClient& http, const String& curren * @param md5 String * @return true if Update ok */ -bool HTTPUpdate::runUpdate(Stream& in, uint32_t size, String md5, int command) +bool HTTPUpdate::runUpdate(Stream& in, uint32_t size, String crc, int command) { - + mbedtls_md_type_t md_type = MBEDTLS_MD_NONE; StreamString error; - if(!Update.begin(size, command, _ledPin, _ledOn)) { - _lastError = Update.getError(); - Update.printError(error); + if(!_updater->begin(size, command, _ledPin, _ledOn)) { + _lastError = _updater->getError(); + _updater->printError(error); error.trim(); // remove line ending - log_e("Update.begin failed! (%s)\n", error.c_str()); + log_e("_updater->begin failed! (%s)\n", error.c_str()); return false; } - if(md5.length()) { - if(!Update.setMD5(md5.c_str())) { - _lastError = HTTP_UE_SERVER_FAULTY_MD5; - log_e("Update.setMD5 failed! (%s)\n", md5.c_str()); - return false; - } - } - -// To do: the SHA256 could be checked if the server sends it + // Bit of a heuristic + switch(crc.length()) { + case 32: md_type = MBEDTLS_MD_MD5; break; + case 40: md_type = MBEDTLS_MD_SHA1; break; + case 56: md_type = MBEDTLS_MD_SHA224; break; + case 64: md_type = MBEDTLS_MD_SHA256; break; + case 128: md_type = MBEDTLS_MD_SHA512; break; + default: break; + }; + + if(md_type == MBEDTLS_MD_NONE || _processor->setChecksum(crc.c_str(), md_type)) { + _lastError = HTTP_UE_SERVER_FAULTY_MD5; + log_e("Updater::runUpdate failed - cannot handle this checksum (%s, only MD5, SHA1,224,256 and 512 supported as hex string)\n", md5.c_str()); + return false; + }; - if(Update.writeStream(in) != size) { - _lastError = Update.getError(); - Update.printError(error); + if(_updater->writeStream(in) != size) { + _lastError = _updater->getError(); + _updater->printError(error); error.trim(); // remove line ending - log_e("Update.writeStream failed! (%s)\n", error.c_str()); + log_e("_updater->writeStream failed! (%s)\n", error.c_str()); return false; } - if(!Update.end()) { - _lastError = Update.getError(); - Update.printError(error); + if(!_updater->end()) { + _lastError = _updater->getError(); + _updater->printError(error); error.trim(); // remove line ending - log_e("Update.end failed! (%s)\n", error.c_str()); + log_e("_updater->end failed! (%s)\n", error.c_str()); return false; } diff --git a/libraries/HTTPUpdate/src/HTTPUpdate.h b/libraries/HTTPUpdate/src/HTTPUpdate.h index e58ca837aa0..dac29a3b607 100644 --- a/libraries/HTTPUpdate/src/HTTPUpdate.h +++ b/libraries/HTTPUpdate/src/HTTPUpdate.h @@ -95,6 +95,9 @@ class HTTPUpdate int _ledPin; uint8_t _ledOn; + UpdateProcessorWithChecksum * _processor ; + UpdateClass * _updater; + const mbedtls_md_info_t *_md_info; mbedtls_md_context_t * _md_ctx; }; diff --git a/libraries/Update/src/UpdateProcessor.cpp b/libraries/Update/src/UpdateProcessor.cpp index 7ffab100f33..23ac894bbb9 100644 --- a/libraries/Update/src/UpdateProcessor.cpp +++ b/libraries/Update/src/UpdateProcessor.cpp @@ -36,3 +36,117 @@ UpdateProcessor::secure_update_processor_err_t UpdateProcessorLegacy::process_he return UpdateProcessor::secure_update_processor_ERROR; }; + +UpdateProcessorWithChecksum:: ~UpdateProcessorWithChecksum() { + if (_md_ctx) + mbedtls_md_free(_md_ctx); +}; + +UpdateProcessor::secure_update_processor_err_t UpdateProcessorWithChecksum::setChecksum(const char * crc, mbedtls_md_type_t md_type) { + const mbedtls_md_info_t * mdt; + + if (md_type == MBEDTLS_MD_NONE) { + switch(strlen(crc)) { + case 32: md_type = MBEDTLS_MD_MD5; break; + case 40: md_type = MBEDTLS_MD_SHA1; break; + case 56: md_type = MBEDTLS_MD_SHA224; break; + case 64: md_type = MBEDTLS_MD_SHA256; break; + case 128: md_type = MBEDTLS_MD_SHA512; break; + default: + log_e("Checksum type not recognized (from its length)"); + return secure_update_processor_ERROR; + break; + }; + log_d("Auto detected checksum based on length."); + }; + + if (!(mdt = mbedtls_md_info_from_type(md_type))) { + log_e("Checksum type %d supported/compiled in", md_type); + return secure_update_processor_ERROR; + }; + + size_t len = mbedtls_md_get_size(mdt); + if (len*2 != strlen(crc)) { + log_e("Checksum not the right length for a %s checksum in hex", mbedtls_md_get_name(mdt)); + return secure_update_processor_ERROR; + }; + + log_d("Checksum %s parsed as %s", crc, mbedtls_md_get_name(mdt)); + + for(int i = 0; i < len * 2; i++) { + unsigned char c = crc[i]; + if (c>='0' && c<='9') + c -= '0'; + else if (c >='a' && c<='f') + c = c - 'a' + 10; + else if (c >='A'&& c<='F') + c = c - 'A' + 10; + else { + log_e("Hex Checksum contains illegal chars"); + return secure_update_processor_ERROR; + }; + if (i & 1) + _md[i>>1] |= c; + else + _md[i>>1] = c<<4; + }; + + if ((_md_ctx = (mbedtls_md_context_t*)malloc(sizeof(*_md_ctx))) == NULL) { + log_e("Checksum - could not claim memory"); + return secure_update_processor_ERROR; + }; + mbedtls_md_init(_md_ctx); + if ( + (mbedtls_md_setup(_md_ctx, _md_info, 0) != 0) || + (mbedtls_md_starts(_md_ctx) != 0) + ) { + log_e("Checksum - setup problem"); + return secure_update_processor_ERROR; + }; +} + +void UpdateProcessorWithChecksum::reset() { + if (_md_ctx) + mbedtls_md_starts(_md_ctx); +} + +UpdateProcessor::secure_update_processor_err_t UpdateProcessorWithChecksum::process_payload(uint8_t * buffer, size_t *len) { + if (mbedtls_md_update(_md_ctx, buffer, *len)) { + log_e("Failed to update digest."); + return secure_update_processor_ERROR; + } + return secure_update_processor_OK; +}; + +UpdateProcessor::secure_update_processor_err_t UpdateProcessorWithChecksum::process_end() { + unsigned char buff[MBEDTLS_MD_MAX_SIZE], digest[MBEDTLS_MD_MAX_SIZE]; + + if (mbedtls_md_finish(_md_ctx, buff)) { + log_e("Failed to finalize digest."); + return secure_update_processor_ERROR; + } + +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG + String out = ""; + for (int i = 0; i < mbedtls_md_get_size(_md_info); i++) + out += String(buff[i], HEX); + log_d("Payload calculated %s Digest %d:%s", mbedtls_md_get_name(_md_info), mbedtls_md_get_size(_md_info), out.c_str()); +#endif + + int res = memcmp(_md, buff, mbedtls_md_get_size(_md_info)); + + mbedtls_md_free(_md_ctx); + free(_md_ctx); + _md_ctx = NULL; + + // compare this with the digest we got from the signed TSInfo + if (res) { + log_e("Payload digest does NOT match."); + return UpdateProcessor::secure_update_processor_ERROR; + } + log_d("Payload digest matches."); + return secure_update_processor_OK; +} + + + diff --git a/libraries/Update/src/UpdateProcessor.h b/libraries/Update/src/UpdateProcessor.h index cf294e5f86a..bc65f09f7a6 100644 --- a/libraries/Update/src/UpdateProcessor.h +++ b/libraries/Update/src/UpdateProcessor.h @@ -48,4 +48,21 @@ class UpdateProcessorLegacy : public UpdateProcessor { return secure_update_processor_OK; }; }; + +class UpdateProcessorWithChecksum : public UpdateProcessor { + public: + UpdateProcessorWithChecksum() : _md_info(NULL), _md_ctx(NULL) {}; + ~UpdateProcessorWithChecksum(); + void reset(); + secure_update_processor_err_t setChecksum(const char * crc, mbedtls_md_type_t md_type = MBEDTLS_MD_NONE); + secure_update_processor_err_t process_header(uint32_t *command, uint8_t * buffer, size_t *len) { + return secure_update_processor_OK; + }; + secure_update_processor_err_t process_payload(uint8_t * buff, size_t *len); + secure_update_processor_err_t process_end(); + private: + const mbedtls_md_info_t *_md_info; + mbedtls_md_context_t * _md_ctx; + unsigned char _md[MBEDTLS_MD_MAX_SIZE]; +}; #endif diff --git a/libraries/Update/src/Updater.cpp b/libraries/Update/src/Updater.cpp index 745855b132f..8c47982a52c 100644 --- a/libraries/Update/src/Updater.cpp +++ b/libraries/Update/src/Updater.cpp @@ -300,6 +300,7 @@ bool UpdateClass::activate() { } _reset(); +#if 0 if (1) { unsigned char buff[MBEDTLS_MD_MAX_SIZE]; mbedtls_md_context_t ctx; @@ -328,6 +329,7 @@ bool UpdateClass::activate() { log_d("RAW %s Digest %s", mbedtls_md_get_name(t), out.c_str()); mbedtls_md_free(&ctx); } +#endif return true; } else if (_command == U_SPIFFS) { From 9ce71afa2a24b5e76470d6d4568f6ccd1eeda9ee Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Tue, 21 Jul 2020 08:35:57 +0200 Subject: [PATCH 16/24] Fix exit code. --- libraries/Update/src/UpdateProcessor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/Update/src/UpdateProcessor.cpp b/libraries/Update/src/UpdateProcessor.cpp index 23ac894bbb9..4c7fa3251f3 100644 --- a/libraries/Update/src/UpdateProcessor.cpp +++ b/libraries/Update/src/UpdateProcessor.cpp @@ -103,6 +103,7 @@ UpdateProcessor::secure_update_processor_err_t UpdateProcessorWithChecksum::setC log_e("Checksum - setup problem"); return secure_update_processor_ERROR; }; + return secure_update_processor_OK; } void UpdateProcessorWithChecksum::reset() { From 62cfb7ec465b308ff8ed719efcfb0d68b8a0757b Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Tue, 21 Jul 2020 09:15:51 +0200 Subject: [PATCH 17/24] Remove test key - to avoid confusion/superfluous alerts of scanning tools --- tools/x | 47 ----------------------------------------------- 1 file changed, 47 deletions(-) delete mode 100644 tools/x diff --git a/tools/x b/tools/x deleted file mode 100644 index 78572e0931d..00000000000 --- a/tools/x +++ /dev/null @@ -1,47 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC+Frs/k3M/Szbs -XCBtEOoOPKxA1zQ4/UUoliiIbPlrr2Bgg6CAHYV83BRjjse5jy3+fGVwspQwu04Y -Z5KBVQgm2mvGppzBMolTa/9xc2Xk7BXxSv7NnouUqao/UDrctn46q/dzrkYLNoPw -Hrrf6COwhQow4ZNSwozkSNVCBjaCcRNU72JnG9Sirs3Qk1JMfe3/6V82VoU9t6qZ -DnEYe9oJ41zzyITNN69hkSLdGp1gT2S2Wqr/Fas0CSzSwW7V4yewM3iRztEpirG3 -V3Kq/hnmlK6yBWstEja3GUEITZ9RFo4MQE18Kq0fN2OR/gduwx1qp1t9+a6R0ICx -F8eKSouBAgMBAAECggEAan55sQh+jHsq7gg+8luDhS5gooLucu8Ri8d0Ndo3cijy -qZ+Uj7H6UxVJJu2a305jyisO+bBSbWrCOdHstiBZTMsZjlVRhLs3TFE47upjr3jE -YkZNsgyczlCvXIEqOPTUizJEk76S5z1HdYMB1udK913Rc8ksrDTqkgprz22DpFeV -QrxEUZHaXF8QkAsmQK8IvUh36cQ3rljEt61W1lTn6EgiKEBdjzlS5T8ByX/s9afH -MPA8FWvi7pF1v+A8xF3duEUFwlu8MQSeLpdCfnbgmgh9xAIAcveuq/ygbeTXcjG3 -X2k2IA5QjaGZLPFOLANSsLdatpWR0EiTUY3tstQQAQKBgQDuJuvlwXJnnmDVSreZ -LG6w+Usn8Ll+oCkVogGhuEOS7wJDmlLgjyortkg7hiNpH/S8D/EQrTcK1OBQ1f6M -jndCqRwiI72bfI6RmOtFUuFgGKVv1PM598ny91ooVbPnFLlABaPY4zxGz7629b2r -4Xm4Y7XVWBivomfAm1JBHoHr4QKBgQDMVbCpM760yh3avitxpRzPjm7EYON+Pp37 -ypt5+tCDJLI6LEnXj/kHSnA1AlYRYNAKdFOXNmYANi/UXTfSNdUi0QWOxYTg6dzP -lCm8dlzvPB6MSDJ8QLAyxDa2c8/RAjTMRlzwlhx/RarFxu7s/Nz35O587krrF7iZ -+QlUjIOToQKBgEmPZiACn6bX6csYGBvM+KfbaNZ+aZ1uNSKEdwKP2veAse9VtLG+ -JuV3mVohdcCb6UlJl0lyZ2UGGf9+CfE6dCX3/EOjTIbBc3wPguRX1FuNYVRor3RB -UHSm6Ic66aW/5fSbiV+N2Ol6c8spnpt/usw0qE6stntRq8B3eXG5zaGhAoGBAI8J -qTo4+9Xo6s82cokCcVGmPkoAHSBByyv4n6/a8N3s8UqhuCtLIKOG61dgREm0AM5g -sidXMD4TsJWzj6D6iDHwKjjQcf1UGHvDFxhTryjVZ/kBjT3HttBC5J4CIkcIiVc6 -JlNQcbByX2JFqPmC5VgvDPethApgN5ypZWunVXAhAoGAGVrAHCAbAq36p50q9Hlm -sqiincoZOjwUsZ8yHYheGnGG12vu3SMh/erURnIrblDkv29X3VjoRxY740MikfzF -n3QTw+DAXjG2s5sqlCAPIb/BYbGgXD8fwcK6zKPizlEVEXWR3gZxci1hToVoNnT0 -4leVaukit+L19bgJkaTF3Gg= ------END PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIIDIzCCAgugAwIBAgIUKhLblYDQkAn8rjxdX3krTB5/liQwDQYJKoZIhvcNAQEL -BQAwFTETMBEGA1UEAwwKQXJkdWlub09UQTAeFw0yMDA0MjAwOTIyMjBaFw0yMDA1 -MjAwOTIyMjBaMBUxEzARBgNVBAMMCkFyZHVpbm9PVEEwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQC+Frs/k3M/SzbsXCBtEOoOPKxA1zQ4/UUoliiIbPlr -r2Bgg6CAHYV83BRjjse5jy3+fGVwspQwu04YZ5KBVQgm2mvGppzBMolTa/9xc2Xk -7BXxSv7NnouUqao/UDrctn46q/dzrkYLNoPwHrrf6COwhQow4ZNSwozkSNVCBjaC -cRNU72JnG9Sirs3Qk1JMfe3/6V82VoU9t6qZDnEYe9oJ41zzyITNN69hkSLdGp1g -T2S2Wqr/Fas0CSzSwW7V4yewM3iRztEpirG3V3Kq/hnmlK6yBWstEja3GUEITZ9R -Fo4MQE18Kq0fN2OR/gduwx1qp1t9+a6R0ICxF8eKSouBAgMBAAGjazBpMB0GA1Ud -DgQWBBRiTts814ii31hX1R1uIYb8iM4llzAfBgNVHSMEGDAWgBRiTts814ii31hX -1R1uIYb8iM4llzAPBgNVHRMBAf8EBTADAQH/MBYGA1UdJQEB/wQMMAoGCCsGAQUF -BwMIMA0GCSqGSIb3DQEBCwUAA4IBAQBfw6lpJyoPVMt2pJ/dXQr9ehvYoIRnsjRd -VckcMifoSbQloxOoxPRY5dOFoELdVeV+dK3kUZGiKTUOHuogIsMTZ28XfSw4Xsrz -efEwpRrSThf3DUVNsP/uH5BpSRaGT72rMFm8G9i19TUpf0wY58OvlNumHwBWaDa3 -4tKpePYAxzJyWLIm/KmSfMxKWldIMHVLXtkRLON1K2HIh32ikN39KeciDLs8Uh9i -Ufueb28/2u7iTCLfsOuSG/E5AKyrkNwW8wcjP8vEIpek1J+OORfZ5Dz5Mkwqt73b -7pRMn+ybGNU4RA8vrAtVk6zfoW5uUP+ZXxRu7vsmCwuzyNDn1+Ca ------END CERTIFICATE----- From 73500236f9e5cd1f72cb7318d1d94b21e10cbbaf Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Tue, 21 Jul 2020 20:21:01 +0200 Subject: [PATCH 18/24] Fix debugging output. --- libraries/Update/src/UpdateProcessorRFC3161.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/libraries/Update/src/UpdateProcessorRFC3161.cpp b/libraries/Update/src/UpdateProcessorRFC3161.cpp index ada641d4466..969f302ff01 100644 --- a/libraries/Update/src/UpdateProcessorRFC3161.cpp +++ b/libraries/Update/src/UpdateProcessorRFC3161.cpp @@ -255,20 +255,20 @@ UpdateProcessor::secure_update_processor_err_t UpdateProcessorRFC3161::process_h { log_d("Signatures in the trust chain:"); for (mbedtls_x509_crt * c = _trustChain; c; c = c->next) { - char buf[1024 * 2]; - mbedtls_x509_crt_info(buf, sizeof(buf), " - ", c); - log_d(" %s", buf); + char buff[1024 * 2]; + mbedtls_x509_crt_info(buf, sizeof(buff), " - ", c); + log_d(" %s", buff); mbedtls_x509_crt_fprint(buff, sizeof(buff)," - ", c, MBEDTLS_MD_SHA256); - log_d(" %s", buf); + log_d(" %s", buff); }; log_d("Signatures in the RFC3161 wrapper:"); for (mbedtls_x509_crt * c = _reply.chain; c; c = c->next) { - char buf[1024 * 2]; - mbedtls_x509_crt_info(buf, sizeof(buf), " - ", c); - log_d(" %s", buf); + char buff[1024 * 2]; + mbedtls_x509_crt_info(buf, sizeof(buff), " - ", c); + log_d(" %s", buff); mbedtls_x509_crt_fprint(buff, sizeof(buff)," - ", c, MBEDTLS_MD_SHA256); - log_d(" %s", buf); + log_d(" %s", buff); }; } #endif From 849423629add23439276a1f09a972d5ade02bc2f Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Sat, 25 Jul 2020 19:46:47 +0200 Subject: [PATCH 19/24] Reduce debug output; fix fingerprint printing. --- .../src/mbedtls-ts-addons/signer_info.cpp | 30 +---- libraries/Update/src/mbedtls-ts-addons/ts.cpp | 113 +++++------------- .../src/mbedtls-ts-addons/x509_crt_utils.cpp | 13 +- .../src/mbedtls-ts-addons/x509_crt_utils.h | 2 +- .../src/mbedtls-ts-addons/x509_ts_utils.cpp | 11 +- .../src/mbedtls-ts-addons/x509_ts_utils.h | 3 +- 6 files changed, 51 insertions(+), 121 deletions(-) diff --git a/libraries/Update/src/mbedtls-ts-addons/signer_info.cpp b/libraries/Update/src/mbedtls-ts-addons/signer_info.cpp index 0b7b191d88a..bc30c42f4ec 100644 --- a/libraries/Update/src/mbedtls-ts-addons/signer_info.cpp +++ b/libraries/Update/src/mbedtls-ts-addons/signer_info.cpp @@ -70,7 +70,7 @@ int mbedtls_ts_get_signer_info(unsigned char **p, unsigned char * end, mbedtls_a { char s[1024]; mbedtls_x509_dn_gets(s, sizeof(s), &(out.sid_name)); - _TS_DEBUG_PRINTF("SID name %s\n", s); + _TS_DEBUG_PRINTF("SID name %s", s); }; #endif @@ -100,7 +100,7 @@ int mbedtls_ts_get_signer_info(unsigned char **p, unsigned char * end, mbedtls_a out.signed_attribute_raw = sap; unsigned char *ep = *p + len; - _TS_DEBUG_PRINTF("Signed attributes:\n"); + _TS_DEBUG_PRINTF("Signed attributes:"); while (*p < ep) { /* Attribute ::= SEQUENCE { attrType OBJECT IDENTIFIER, @@ -115,7 +115,7 @@ int mbedtls_ts_get_signer_info(unsigned char **p, unsigned char * end, mbedtls_a CHKR(mbedtls_asn1_get_tag(p, ep, &len, MBEDTLS_ASN1_OID) ); - _TS_DEBUG_PRINTF(" %s:\n", _oidbuff2str(*p, len)); + _TS_DEBUG_PRINTF(" %s:", _oidbuff2str(*p, len)); mbedtls_x509_buf oid; oid.p = *p; @@ -132,11 +132,6 @@ int mbedtls_ts_get_signer_info(unsigned char **p, unsigned char * end, mbedtls_a CHKR(out.sig_digest != NULL); CHKR(mbedtls_asn1_get_tag(p, evap, &len, MBEDTLS_ASN1_OCTET_STRING) ); -#ifdef _TS_DEBUG - _TS_DEBUG_PRINTF(" Digest (signed section): len=%d: ", len); - for (int i = 0; i < len; i++) _TS_DEBUG_PRINTF("%02x", (*p)[i]); - _TS_DEBUG_PRINTF(".\n"); -#endif // Only pick it if it is longer. if (len > out.sig_digest_len) { out.sig_digest = *p; @@ -154,11 +149,6 @@ int mbedtls_ts_get_signer_info(unsigned char **p, unsigned char * end, mbedtls_a out.signing_cert_hash = *p; out.signing_cert_hash_len = len; }; -#ifdef _TS_DEBUG - _TS_DEBUG_PRINTF(" Signing cert hash V1 (%d): ", out.signing_cert_hash_type); - for (int i = 0; i < len; i++) _TS_DEBUG_PRINTF("%02x", out.signing_cert_hash[i]); - _TS_DEBUG_PRINTF(".\n"); -#endif } else if ( MBEDTLS_OID_CMP( MBEDTLS_OID_PKCS9_SMIME_AA_SIGNING_CERT_V2, &oid ) == 0 ) { /* SigningCertificateV2 ::= SEQUENCE @@ -187,12 +177,9 @@ int mbedtls_ts_get_signer_info(unsigned char **p, unsigned char * end, mbedtls_a out.signing_cert_hash = *p; out.signing_cert_hash_len = len; - _TS_DEBUG_PRINTF(" Signing cert hash (%d) V2: ", out.signing_cert_hash_type); - for (int i = 0; i < len; i++) _TS_DEBUG_PRINTF("%02x", out.signing_cert_hash[i]); - _TS_DEBUG_PRINTF(".\n"); } else { - _TS_DEBUG_PRINTF(" Not decoded -- skipped\n"); + _TS_DEBUG_PRINTF(" Not decoded -- skipped"); } // skip over all else. *p = evap; @@ -212,15 +199,6 @@ int mbedtls_ts_get_signer_info(unsigned char **p, unsigned char * end, mbedtls_a out.sig = *p; out.sig_len = len; -#ifdef _TS_DEBUG - _TS_DEBUG_PRINTF("Signature (%d bytes): ", out.sig_len); - for (int i = 0; i < out.sig_len; i++) { - _TS_DEBUG_PRINTF("%02x", out.sig[i]); - } - _TS_DEBUG_PRINTF(".\n"); -#endif - - /* unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL */ *p = eap; // skip. diff --git a/libraries/Update/src/mbedtls-ts-addons/ts.cpp b/libraries/Update/src/mbedtls-ts-addons/ts.cpp index d2ba505f2bc..9565233a1cb 100644 --- a/libraries/Update/src/mbedtls-ts-addons/ts.cpp +++ b/libraries/Update/src/mbedtls-ts-addons/ts.cpp @@ -18,8 +18,8 @@ #ifdef _TS_DEBUG #define CHKR(r) { if ( ( ret = (r) ) != 0 ) { assert(0); return ret; }; } #define CHKRE(r,e) { if ( (r) != 0 ) { assert(0); return e; }; } -#define CHK(r) { if ( ( ret = (r) ) != 0 ) { _TS_DEBUG_PRINTF("At @%d in rfc\n", p-from); assert(0); goto ts_reply_free_and_exit; }; } -#define CHKE(r,e) { if ( ( (r) ) != 0 ) { _TS_DEBUG_PRINTF("At @%d in rfc\n", p-from); assert(0); ret = (e); goto ts_reply_free_and_exit; }; } +#define CHK(r) { if ( ( ret = (r) ) != 0 ) { _TS_DEBUG_PRINTF("At @%d in rfc", p-from); assert(0); goto ts_reply_free_and_exit; }; } +#define CHKE(r,e) { if ( ( (r) ) != 0 ) { _TS_DEBUG_PRINTF("At @%d in rfc", p-from); assert(0); ret = (e); goto ts_reply_free_and_exit; }; } #else #define CHKR(r) { if ( ( ret = (r) ) != 0 ) { return ret; }; } #define CHKRE(r,e) { if ( (r) != 0 ) { return (e); }; } @@ -73,7 +73,7 @@ int mbedtls_asn1_get_ts_pkistatusinfo(unsigned char **p, unsigned char * end, mb CHKR(mbedtls_asn1_get_int( p, end, &(out.pki_status))); - _TS_DEBUG_PRINTF("PKIStatus: %d\n", out.pki_status); + _TS_DEBUG_PRINTF("PKIStatus: %d", out.pki_status); if (*p < endp) { if (mbedtls_asn1_get_tag(p, *p + len, &len, @@ -102,12 +102,12 @@ int mbedtls_asn1_get_ts_pkistatusinfo(unsigned char **p, unsigned char * end, mb }; *p += len; }; - _TS_DEBUG_PRINTF("PKIFreeText: %s\n", statusBuff); + _TS_DEBUG_PRINTF("PKIFreeText: %s", statusBuff); } mbedtls_x509_bitstring bs; if ( mbedtls_asn1_get_bitstring(p, *p + len, &bs ) == 0 ) { - _TS_DEBUG_PRINTF("PKIFailureInfo: %s\n", _bitstr(&bs)); + _TS_DEBUG_PRINTF("PKIFailureInfo: %s", _bitstr(&bs)); }; CHKRE((bs.len > 1), MBEDTLS_ERR_X509_INVALID_FORMAT); @@ -172,7 +172,7 @@ int mbedtls_ts_get_tsinfo(unsigned char **p, unsigned char * end, mbedtls_asn1_t CHKRE((v != 1), MBEDTLS_ERR_X509_INVALID_FORMAT); - _TS_DEBUG_PRINTF("TSTInfo Version %d\n", v); + _TS_DEBUG_PRINTF("TSTInfo Version %d", v); // policy TSAPolicyId, CHKR(mbedtls_asn1_get_tag(p, end_tsinfo, &len, MBEDTLS_ASN1_OID) ); @@ -192,18 +192,12 @@ int mbedtls_ts_get_tsinfo(unsigned char **p, unsigned char * end, mbedtls_asn1_t out.payload_digest_len = len; *p += len; -#ifdef _TS_DEBUG - _TS_DEBUG_PRINTF("The main hash of the payload: len=%d: ", out.payload_digest_len); - for (int i = 0; i < out.payload_digest_len; i++) _TS_DEBUG_PRINTF("%0x", out.payload_digest[i]); - _TS_DEBUG_PRINTF(".\n"); -#endif - // serialNumber INTEGER, { uint64_t serial = 0; CHKR(mbedtls_asn1_get_uint64(p, end_tsinfo, &serial)); - _TS_DEBUG_PRINTF("Serial %llu\n", serial); + _TS_DEBUG_PRINTF("Serial %llu", serial); }; /* genTime GeneralizedTime, @@ -215,7 +209,7 @@ int mbedtls_ts_get_tsinfo(unsigned char **p, unsigned char * end, mbedtls_asn1_t CHKR(x509_parse_time( p, len, 4, &(out.signed_time))); - _TS_DEBUG_PRINTF("Signature timestap: %04d-%02d-%02d %02d:%02d:%02d UTC\n", + _TS_DEBUG_PRINTF("Signature timestap: %04d-%02d-%02d %02d:%02d:%02d UTC", out.signed_time.year, out.signed_time.mon, out.signed_time.day, out.signed_time.hour, out.signed_time.min, out.signed_time.sec); if (mbedtls_asn1_get_tag( p, end_tsinfo, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) == 0) { @@ -227,7 +221,7 @@ int mbedtls_ts_get_tsinfo(unsigned char **p, unsigned char * end, mbedtls_asn1_t if (tryit == 0) tryit = mbedtls_asn1_get_int( p, ep, &milli); if (tryit == 0) tryit = mbedtls_asn1_get_int( p, ep, µ); - _TS_DEBUG_PRINTF("Accuracy %d.%d.%d (ignored %d bytes)\n", sec, milli, micro, ep - *p); + _TS_DEBUG_PRINTF("Accuracy %d.%d.%d (ignored %d bytes)", sec, milli, micro, ep - *p); // skip over any extra cruft (Observed at the BSI.de) *p = ep; @@ -237,7 +231,7 @@ int mbedtls_ts_get_tsinfo(unsigned char **p, unsigned char * end, mbedtls_asn1_t // ordering BOOLEAN DEFAULT FALSE, int ordering = 0; if (mbedtls_asn1_get_bool( p, end_tsinfo, &ordering) == 0) { - _TS_DEBUG_PRINTF("Ordering: %d\n", ordering); + _TS_DEBUG_PRINTF("Ordering: %d", ordering); } } // nonce INTEGER OPTIONAL, @@ -274,7 +268,7 @@ int mbedtls_ts_get_tsinfo(unsigned char **p, unsigned char * end, mbedtls_asn1_t return ret; char s[1024]; mbedtls_x509_dn_gets(s, sizeof(s), &directoryName); - _TS_DEBUG_PRINTF("directoryName: %s\n", s); + _TS_DEBUG_PRINTF("directoryName: %s", s); break; default: assert(0); @@ -287,7 +281,7 @@ int mbedtls_ts_get_tsinfo(unsigned char **p, unsigned char * end, mbedtls_asn1_t if ((((*p)[0]) & 0xF) == 1) if (mbedtls_asn1_get_tag(p, end_tsinfo, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_CONTEXT_SPECIFIC) == 0) { - _TS_DEBUG_PRINTF("Has extensions -- len %d (Unparsed %d)\n", len, end_tsinfo - *p); + _TS_DEBUG_PRINTF("Has extensions -- len %d (Unparsed %d)", len, end_tsinfo - *p); p += len; }; @@ -331,7 +325,7 @@ int mbedtls_ts_reply_free(struct mbedtls_ts_reply * reply) { int mbedtls_x509_ts_reply_parse_der(const unsigned char **buf, size_t * buflenp, mbedtls_ts_reply * reply) { mbedtls_x509_crt ** chain = &(reply->chain), * crt = NULL; - unsigned char *p = NULL, *end = NULL, * from; + unsigned char *p = NULL, *end = NULL; int ret = MBEDTLS_ERR_TS_CORRUPTION_DETECTED; size_t len; @@ -350,8 +344,10 @@ int mbedtls_x509_ts_reply_parse_der(const unsigned char **buf, size_t * buflenp, return ( MBEDTLS_ERR_TS_BAD_INPUT_DATA ); // reuse }; p = (unsigned char *)*buf; - from = p; end = p + *buflenp; +#ifdef _TS_DEBUG + unsigned char *from = p; +#endif /* TimeStampResp ::= SEQUENCE status PKIStatusInfo, @@ -390,7 +386,7 @@ int mbedtls_x509_ts_reply_parse_der(const unsigned char **buf, size_t * buflenp, idSig.len = len; p += len; - _TS_DEBUG_PRINTF("idSig: %3d %s\n", len, _oid2str(&idSig)); + _TS_DEBUG_PRINTF("idSig: %3d %s", len, _oid2str(&idSig)); if ( MBEDTLS_OID_CMP( MBEDTLS_OID_PKCS7_ID_SIGNED, &idSig ) != 0 ) { ret = ( MBEDTLS_ERR_X509_INVALID_FORMAT ); @@ -420,7 +416,7 @@ int mbedtls_x509_ts_reply_parse_der(const unsigned char **buf, size_t * buflenp, { int v; CHK(mbedtls_asn1_get_int( &p, end, &v)); - _TS_DEBUG_PRINTF("SignedData Version %d\n", v); + _TS_DEBUG_PRINTF("SignedData Version %d", v); }; CHK(mbedtls_asn1_get_tag( &p, end, &len, @@ -442,7 +438,7 @@ int mbedtls_x509_ts_reply_parse_der(const unsigned char **buf, size_t * buflenp, eContentType.p = p; eContentType.len = len; p += len; - _TS_DEBUG_PRINTF("eContentType: %3d %s\n", len, _oid2str(&eContentType)); + _TS_DEBUG_PRINTF("eContentType: %3d %s", len, _oid2str(&eContentType)); CHKE((MBEDTLS_OID_CMP( MBEDTLS_OID_STINFO, &eContentType ) != 0 ), MBEDTLS_ERR_X509_INVALID_FORMAT); @@ -472,15 +468,15 @@ int mbedtls_x509_ts_reply_parse_der(const unsigned char **buf, size_t * buflenp, if (mbedtls_asn1_get_tag( &p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_CONTEXT_SPECIFIC) == 0) { CHK(mbedtls_asn1_get_certificate_set(&p, p + len, chain)); } else { - _TS_DEBUG_PRINTF("No Certs\n"); + _TS_DEBUG_PRINTF("No Certs"); }; if ( mbedtls_asn1_get_tag( &p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_CONTEXT_SPECIFIC) == 0) { - _TS_DEBUG_PRINTF("Has CRLs(ignored)\n"); + _TS_DEBUG_PRINTF("Has CRLs(ignored)"); p += len; } else { - _TS_DEBUG_PRINTF("No CRLs\n"); + _TS_DEBUG_PRINTF("No CRLs"); }; // signerInfos SignerInfos <-- @@ -490,21 +486,12 @@ int mbedtls_x509_ts_reply_parse_der(const unsigned char **buf, size_t * buflenp, CHK(mbedtls_ts_get_signer_info(&p, end, &(reply->signer_info))); if (reply->signer_info.signing_cert_hash) { -#if _TS_DEBUG - _TS_DEBUG_PRINTF("Scanning for certs..\n"); - _TS_DEBUG_PRINTF(" hash (%d): ", reply->signer_info.signing_cert_hash_type); - for (int i = 0; i < reply->signer_info.signing_cert_hash_len; i++) { - _TS_DEBUG_PRINTF("%02x", reply->signer_info.signing_cert_hash[i]); - } - _TS_DEBUG_PRINTF(".\n"); -#endif - // XXX set minimal level ? > SHA1 ? const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type( reply->signer_info.signing_cert_hash_type ); assert(md_info); if (reply->signer_info.signing_cert_hash_len != mbedtls_md_get_size(md_info)) { - _TS_DEBUG_PRINTF("Hash of cert / mismatch %d != %d\n", reply->signer_info.signing_cert_hash_len, mbedtls_md_get_size(md_info)); + _TS_DEBUG_PRINTF("Hash of cert / mismatch %d != %d", reply->signer_info.signing_cert_hash_len, mbedtls_md_get_size(md_info)); ret = MBEDTLS_ERR_PK_SIG_LEN_MISMATCH; goto ts_reply_free_and_exit; } @@ -521,20 +508,8 @@ int mbedtls_x509_ts_reply_parse_der(const unsigned char **buf, size_t * buflenp, CHK(mbedtls_md_update(&md, crt->raw.p, crt->raw.len)); CHK(mbedtls_md_finish(&md, digest)); -#ifdef _TS_DEBUG - _TS_DEBUG_PRINTF(" %2d hash: ", d); - for (int i = 0; i < mbedtls_md_get_size(md_info); i++) { - _TS_DEBUG_PRINTF("%02x", digest[i]); - } - _TS_DEBUG_PRINTF(".\n "); - for (int i = 0; i < mbedtls_md_get_size(md_info); i++) { - _TS_DEBUG_PRINTF("%02x", reply->signer_info.signing_cert_hash[i]); - } - _TS_DEBUG_PRINTF(".\n"); -#endif - if (bcmp(digest, reply->signer_info.signing_cert_hash, mbedtls_md_get_size(md_info)) == 0) { - _TS_DEBUG_PRINTF("Found one that matched\n"); + _TS_DEBUG_PRINTF("Found one that matched"); if (*chain != crt) { // not at the head of the chain; now make it so. mbedtls_x509_crt * parent; @@ -549,7 +524,7 @@ int mbedtls_x509_ts_reply_parse_der(const unsigned char **buf, size_t * buflenp, } break; }; - _TS_DEBUG_PRINTF("Nope # %d did not match\n", d); + _TS_DEBUG_PRINTF("Nope # %d did not match", d); }; }; @@ -621,20 +596,11 @@ int mbedtls_x509_ts_reply_parse_der(const unsigned char **buf, size_t * buflenp, CHK(mbedtls_md_update(&md, signed_data, signed_data_len)); CHK(mbedtls_md_finish(&md, signed_data_digest)); - -#if _TS_DEBUG - _TS_DEBUG_PRINTF("Calculated signedData area hash: "); - for (int i = 0; i < mbedtls_md_get_size(md_info); i++) { - _TS_DEBUG_PRINTF("%02x", signed_data_digest[i]); - } - _TS_DEBUG_PRINTF(".\n"); -#endif - if (reply->signer_info.signed_attribute_raw && reply->signer_info.sig_digest) { - _TS_DEBUG_PRINTF("Indirected signing.\n"); + _TS_DEBUG_PRINTF("Indirected signing."); if (reply->signer_info.sig_digest_len != signed_data_digest_len) { - _TS_DEBUG_PRINTF("Digest in signedAttrs has the wrong length %d != %d\n", reply->signer_info.sig_digest_len, signed_data_digest_len); + _TS_DEBUG_PRINTF("Digest in signedAttrs has the wrong length %d != %d", reply->signer_info.sig_digest_len, signed_data_digest_len); ret = MBEDTLS_ERR_PK_SIG_LEN_MISMATCH; goto ts_reply_free_and_exit; }; @@ -685,44 +651,27 @@ int mbedtls_x509_ts_reply_parse_der(const unsigned char **buf, size_t * buflenp, assert(reply->signer_info.sig); assert(reply->signer_info.sig_len); -#ifdef _TS_DEBUG - _TS_DEBUG_PRINTF("Attribute Signed Data (s=%d, l=%d) -- calculated digest: len=%d: ", - reply->signer_info.signed_attribute_raw - from, - reply->signer_info.signed_attribute_len, - signed_data_digest_len); - for (int i = 0; i < signed_data_digest_len; i++) { - _TS_DEBUG_PRINTF("%02x", signed_data_digest[i]); - } - _TS_DEBUG_PRINTF(".\n"); - - _TS_DEBUG_PRINTF("Signed info (%d bytes): ", reply->signer_info.sig_len); - for (int i = 0; i < reply->signer_info.sig_len; i++) { - _TS_DEBUG_PRINTF("%02x", reply->signer_info.sig[i]); - } - _TS_DEBUG_PRINTF(".\n"); -#endif - ret = mbedtls_pk_verify(&(crt->pk), signed_data_digest_type, signed_data_digest, signed_data_digest_len, reply->signer_info.sig, reply->signer_info.sig_len); switch (ret) { case 0: - _TS_DEBUG_PRINTF("Signature on the Reply OK\n"); + _TS_DEBUG_PRINTF("Signature on the Reply OK"); break; case MBEDTLS_ERR_RSA_VERIFY_FAILED: { char buff[128]; mbedtls_strerror(ret, buff, sizeof(buff)); - _TS_DEBUG_PRINTF("Signature FAIL %x: %s\n", -ret, buff); + _TS_DEBUG_PRINTF("Signature FAIL %x: %s", -ret, buff); goto ts_reply_free_and_exit; }; break; case MBEDTLS_ERR_PK_SIG_LEN_MISMATCH: { char buff[128]; mbedtls_strerror(ret, buff, sizeof(buff)); - _TS_DEBUG_PRINTF("Signature config fail %x: %s\n", -ret, buff); + _TS_DEBUG_PRINTF("Signature config fail %x: %s", -ret, buff); goto ts_reply_free_and_exit; }; break; default: - printf("Other error %x %x\n", ret, -ret); + printf("Other error %x %x", ret, -ret); goto ts_reply_free_and_exit; break; }; diff --git a/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.cpp b/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.cpp index 26ad6795764..72749678679 100644 --- a/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.cpp +++ b/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.cpp @@ -3,18 +3,19 @@ #include "mbedtls/platform.h" +#include "x509_ts_utils.h" #include "x509_crt_utils.h" #ifndef MIN #define MIN(a,b) ( ((a) <(b)) ? (a) : (b)) #endif -int mbedtls_x509_crt_fprint(char * buff, size_t len, char * prefix, mbedtls_x509_crt * cert, mbedtls_md_type_t tpe) +int mbedtls_x509_crt_fprint(char * buff, size_t len, const char * prefix, const mbedtls_x509_crt * cert, mbedtls_md_type_t tpe) { const mbedtls_md_info_t * mdt = mbedtls_md_info_from_type(tpe ? tpe : MBEDTLS_MD_SHA256); const char * nme = mbedtls_md_get_name(mdt); const size_t dl = mbedtls_md_get_size(mdt); - const char * txt = " Fingerprint="; + const char * txt = " fingerprint: "; unsigned char *output = NULL; char *p = buff, * ep = buff + len-4; @@ -24,12 +25,14 @@ int mbedtls_x509_crt_fprint(char * buff, size_t len, char * prefix, mbedtls_x509 mbedtls_md_init(&ctx); if ( - ((output = (unsigned char *)mbedtls_calloc(dl,1)) != NULL) || + ((output = (unsigned char *)mbedtls_calloc(dl,1)) == NULL) || ((ret = mbedtls_md_setup(&ctx, mdt, 9)) != 0) || ((ret = mbedtls_md_starts (&ctx)) != 0) || ((ret = mbedtls_md_update (&ctx, cert->raw.p, cert->raw.len)) != 0) || ((ret = mbedtls_md_finish (&ctx, output)) != 0) - ) goto erx; + ) { + goto erx; + }; p = buff; if (prefix) { @@ -66,10 +69,10 @@ int mbedtls_x509_crt_fprint(char * buff, size_t len, char * prefix, mbedtls_x509 // return the bytes written into buff. ret = p - buff; - erx: mbedtls_md_free(&ctx); if (output) mbedtls_free(output); + return ret; } diff --git a/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.h b/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.h index 50e0142d861..082f591a86c 100644 --- a/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.h +++ b/libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.h @@ -24,6 +24,6 @@ * \return The length of the string written (not including the * terminated nul byte), or a negative error code. */ -int mbedtls_x509_crt_fprint(char * buf, size_t size, char * prefix, mbedtls_x509_crt * crt, mbedtls_md_type_t mdtype); +int mbedtls_x509_crt_fprint(char * buf, size_t size, const char * prefix, const mbedtls_x509_crt * crt, mbedtls_md_type_t mdtype); #endif diff --git a/libraries/Update/src/mbedtls-ts-addons/x509_ts_utils.cpp b/libraries/Update/src/mbedtls-ts-addons/x509_ts_utils.cpp index 0363c4ccb83..6dc097b76d5 100644 --- a/libraries/Update/src/mbedtls-ts-addons/x509_ts_utils.cpp +++ b/libraries/Update/src/mbedtls-ts-addons/x509_ts_utils.cpp @@ -8,7 +8,6 @@ #define mbedtls_free free #define mbedtls_calloc calloc #define mbedtls_printf _TS_DEBUG_PRINTF -#define mbedtls_snprintf _TS_DEBUG_SNPRINTF #ifdef _TS_DEBUG char * _oid2str(mbedtls_x509_buf *oid) { @@ -259,7 +258,7 @@ int x509_parse_time( unsigned char **p, size_t len, size_t yearlen, }; if ( 0 != len ) { - _TS_DEBUG_PRINTF("At the end with %d left %c %c %c\n", len, (*p)[0], (*p)[1], (*p)[2]); + _TS_DEBUG_PRINTF("At the end with %d left %c %c %c", len, (*p)[0], (*p)[1], (*p)[2]); return ( MBEDTLS_ERR_X509_INVALID_DATE ); }; // CHECK( x509_date_is_valid( tm ) ); @@ -282,16 +281,16 @@ int mbedtls_asn1_get_algorithm_itentifier(unsigned char **p, unsigned char * end mbedtls_x509_buf oid; oid.p = *p; oid.len = len; - _TS_DEBUG_PRINTF("algoritm OID: %s\n", _oid2str(&oid)); + _TS_DEBUG_PRINTF("algoritm OID: %s", _oid2str(&oid)); mbedtls_md_type_t md_alg; mbedtls_pk_type_t pk_alg; if ( ( ret = mbedtls_oid_get_md_alg(&oid, &md_alg)) == 0) { - _TS_DEBUG_PRINTF(" MD algoritm internal num: %d %s\n", md_alg, mbedtls_md_get_name(mbedtls_md_info_from_type(md_alg))); + _TS_DEBUG_PRINTF(" MD algoritm internal num: %d %s", md_alg, mbedtls_md_get_name(mbedtls_md_info_from_type(md_alg))); *alg = (int) md_alg; } else if ( ( ret = mbedtls_oid_get_pk_alg(&oid, &pk_alg)) == 0) { - _TS_DEBUG_PRINTF(" PK algoritm internal num: %d\n", pk_alg); //, mbedtls_pk_get_name(mbedtls_pk_info_from_type(pk_alg))); + _TS_DEBUG_PRINTF(" PK algoritm internal num: %d", pk_alg); //, mbedtls_pk_get_name(mbedtls_pk_info_from_type(pk_alg))); *alg = (int) pk_alg; } else { _TS_DEBUG_PRINTF("Unkn OID"); @@ -363,7 +362,7 @@ int mbedtls_asn1_get_certificate_set(unsigned char **p, unsigned char * end, mbe #ifdef _TS_DEBUG int i = 0; for (mbedtls_x509_crt * crt = *chain; crt; crt = crt->next) i++; - _TS_DEBUG_PRINTF("Extracted %d certs\n", i); + _TS_DEBUG_PRINTF("Extracted %d certs", i); #endif assert(end == *p); diff --git a/libraries/Update/src/mbedtls-ts-addons/x509_ts_utils.h b/libraries/Update/src/mbedtls-ts-addons/x509_ts_utils.h index 34bd2f71547..94067db2399 100644 --- a/libraries/Update/src/mbedtls-ts-addons/x509_ts_utils.h +++ b/libraries/Update/src/mbedtls-ts-addons/x509_ts_utils.h @@ -36,10 +36,11 @@ int x509_parse_time( unsigned char **p, size_t len, size_t yearlen, #endif #ifdef _TS_DEBUG +#include char * _oid2str(mbedtls_x509_buf *oid); char * _oidbuff2str(unsigned char *buf, size_t len); char * _bitstr(mbedtls_asn1_bitstring *bs); -#define _TS_DEBUG_PRINTF(...) printf( __VA_ARGS__ ) +#define _TS_DEBUG_PRINTF(...) log_d( __VA_ARGS__ ) #else #define _TS_DEBUG_PRINTF(...) /* no debugging compiled in */ #endif From 47c2af5775035badf2bcecca94e0de4fd8d24943 Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Sat, 25 Jul 2020 19:54:42 +0200 Subject: [PATCH 20/24] Separate out the updates; improve documentation, reintroduce the MD5 legacy method for SD/HTTP based updating. --- libraries/Update/src/UpdateProcessor.h | 29 ----------- .../Update/src/UpdateProcessorLegacy.cpp | 25 ++++++++++ libraries/Update/src/UpdateProcessorLegacy.h | 19 ++++++++ .../Update/src/UpdateProcessorRFC3161.cpp | 46 +++++++++--------- ...or.cpp => UpdateProcessorWithChecksum.cpp} | 48 ++++++------------- .../Update/src/UpdateProcessorWithChecksum.h | 27 +++++++++++ libraries/Update/src/Updater.cpp | 5 +- tools/digital-signing.md | 8 +++- 8 files changed, 119 insertions(+), 88 deletions(-) create mode 100644 libraries/Update/src/UpdateProcessorLegacy.cpp create mode 100644 libraries/Update/src/UpdateProcessorLegacy.h rename libraries/Update/src/{UpdateProcessor.cpp => UpdateProcessorWithChecksum.cpp} (76%) create mode 100644 libraries/Update/src/UpdateProcessorWithChecksum.h diff --git a/libraries/Update/src/UpdateProcessor.h b/libraries/Update/src/UpdateProcessor.h index bc65f09f7a6..80f13534903 100644 --- a/libraries/Update/src/UpdateProcessor.h +++ b/libraries/Update/src/UpdateProcessor.h @@ -36,33 +36,4 @@ class UpdateProcessor { virtual secure_update_processor_err_t process_payload(uint8_t * buff, size_t *len); virtual secure_update_processor_err_t process_end(); }; - -class UpdateProcessorLegacy : public UpdateProcessor { - public: - void reset() {}; - secure_update_processor_err_t process_header(uint32_t *command, uint8_t * buffer, size_t *len); - secure_update_processor_err_t process_payload(uint8_t * buff, size_t *len) { - return secure_update_processor_OK; - } - secure_update_processor_err_t process_end() { - return secure_update_processor_OK; - }; -}; - -class UpdateProcessorWithChecksum : public UpdateProcessor { - public: - UpdateProcessorWithChecksum() : _md_info(NULL), _md_ctx(NULL) {}; - ~UpdateProcessorWithChecksum(); - void reset(); - secure_update_processor_err_t setChecksum(const char * crc, mbedtls_md_type_t md_type = MBEDTLS_MD_NONE); - secure_update_processor_err_t process_header(uint32_t *command, uint8_t * buffer, size_t *len) { - return secure_update_processor_OK; - }; - secure_update_processor_err_t process_payload(uint8_t * buff, size_t *len); - secure_update_processor_err_t process_end(); - private: - const mbedtls_md_info_t *_md_info; - mbedtls_md_context_t * _md_ctx; - unsigned char _md[MBEDTLS_MD_MAX_SIZE]; -}; #endif diff --git a/libraries/Update/src/UpdateProcessorLegacy.cpp b/libraries/Update/src/UpdateProcessorLegacy.cpp new file mode 100644 index 00000000000..2d746b038dc --- /dev/null +++ b/libraries/Update/src/UpdateProcessorLegacy.cpp @@ -0,0 +1,25 @@ +#include "Arduino.h" +#include "esp_spi_flash.h" +#include "esp_ota_ops.h" +#include "esp_image_format.h" +#include "esp_partition.h" +#include "esp_spi_flash.h" + +#include "UpdateProcessorLegacy.h" + +UpdateProcessor::secure_update_processor_err_t UpdateProcessorLegacy::process_header(uint32_t *command, uint8_t* buffer, size_t *len) { + if ((*command) == U_SPIFFS) + return UpdateProcessor::secure_update_processor_OK; + + if ((*command) == U_FLASH) { + if (buffer[0] == ESP_IMAGE_HEADER_MAGIC) { + log_d("Valid magic at start of flash header"); + return UpdateProcessor::secure_update_processor_OK; + }; + log_e("Missing ESP_IMAGE_HEADER_MAGIC"); + }; + log_e("Invalid command 0x%04x ", *command); + + return UpdateProcessor::secure_update_processor_ERROR; +}; + diff --git a/libraries/Update/src/UpdateProcessorLegacy.h b/libraries/Update/src/UpdateProcessorLegacy.h new file mode 100644 index 00000000000..b616fae5c66 --- /dev/null +++ b/libraries/Update/src/UpdateProcessorLegacy.h @@ -0,0 +1,19 @@ +#ifndef _UPDATE_PROCESSOR_LEGACY +#define _UPDATE_PROCESSOR_LEGACY +#include "UpdateProcessor.h" + + +#define UPDATE_SIZE_UNKNOWN 0xFFFFFFFF + +class UpdateProcessorLegacy : public UpdateProcessor { + public: + void reset() {}; + secure_update_processor_err_t process_header(uint32_t *command, uint8_t * buffer, size_t *len); + secure_update_processor_err_t process_payload(uint8_t * buff, size_t *len) { + return secure_update_processor_OK; + } + secure_update_processor_err_t process_end() { + return secure_update_processor_OK; + }; +}; +#endif diff --git a/libraries/Update/src/UpdateProcessorRFC3161.cpp b/libraries/Update/src/UpdateProcessorRFC3161.cpp index 969f302ff01..11b60e4d25b 100644 --- a/libraries/Update/src/UpdateProcessorRFC3161.cpp +++ b/libraries/Update/src/UpdateProcessorRFC3161.cpp @@ -9,6 +9,8 @@ #include "mbedtls-ts-addons/x509_crt_utils.h" #include "UpdateProcessorRFC3161.h" +#include "UpdateProcessorLegacy.h" + /* We have three formats; @@ -58,9 +60,6 @@ UpdateProcessorRFC3161::UpdateProcessorRFC3161(UpdateProcessor * nxt) , _trustChain(NULL) , _legacyAllowed(false) { - // we need to hook this in - as the legacy processor does the actual - // burning of the firmware. - // if (_next == NULL) _next = new UpdateProcessorLegacy(); }; @@ -252,25 +251,28 @@ UpdateProcessor::secure_update_processor_err_t UpdateProcessorRFC3161::process_h }; #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG - { - log_d("Signatures in the trust chain:"); - for (mbedtls_x509_crt * c = _trustChain; c; c = c->next) { - char buff[1024 * 2]; - mbedtls_x509_crt_info(buf, sizeof(buff), " - ", c); - log_d(" %s", buff); - mbedtls_x509_crt_fprint(buff, sizeof(buff)," - ", c, MBEDTLS_MD_SHA256); - log_d(" %s", buff); - }; - - log_d("Signatures in the RFC3161 wrapper:"); - for (mbedtls_x509_crt * c = _reply.chain; c; c = c->next) { - char buff[1024 * 2]; - mbedtls_x509_crt_info(buf, sizeof(buff), " - ", c); - log_d(" %s", buff); - mbedtls_x509_crt_fprint(buff, sizeof(buff)," - ", c, MBEDTLS_MD_SHA256); - log_d(" %s", buff); - }; - } + { + char buf[1024 * 2]; + int i; + log_d("Signatures in the trust chain:"); + i = 0; + for (const mbedtls_x509_crt * c = _trustChain; c; c = c->next) { + if ((mbedtls_x509_crt_info( buf, sizeof(buf), " - ", c) > 0) && + (mbedtls_x509_crt_fprint(buf + strlen(buf), sizeof(buf) - strlen(buf), " - ", c, MBEDTLS_MD_SHA256) > 0)) + log_d("* Certificate %d:\n%s", ++i, buf); + else + log_e("Malformed cert in trust chain at postion %d",++i); + }; + log_d("Signatures in the uploaded/untrusted chain:"); + i = 0; + for (const mbedtls_x509_crt * c = _reply.chain; c; c = c->next) { + if ((mbedtls_x509_crt_info( buf, sizeof(buf), " - ", c) > 0) && + (mbedtls_x509_crt_fprint(buf + strlen(buf), sizeof(buf) - strlen(buf), " - ", c, MBEDTLS_MD_SHA256) > 0)) + log_d("* Certificate %d:\n%s", ++i, buf); + else + log_e("Malformed cert in uploaded/untrusted chain at position %d",++i); + }; + } #endif if (mbedtls_x509_crt_verify(_reply.chain, _trustChain, NULL /* no CRL */, NULL /* any name fine */, &results, NULL, NULL) != 0) { char err_mess[128]; diff --git a/libraries/Update/src/UpdateProcessor.cpp b/libraries/Update/src/UpdateProcessorWithChecksum.cpp similarity index 76% rename from libraries/Update/src/UpdateProcessor.cpp rename to libraries/Update/src/UpdateProcessorWithChecksum.cpp index 4c7fa3251f3..d44e488e2af 100644 --- a/libraries/Update/src/UpdateProcessor.cpp +++ b/libraries/Update/src/UpdateProcessorWithChecksum.cpp @@ -6,40 +6,22 @@ #include "esp_partition.h" #include "esp_spi_flash.h" -#include "UpdateProcessor.h" - -/* We have four formats; this handler deals with the original format: - - 1) original 'raw' package. - - Starts with 0xE7 / ESP_IMAGE_HEADER_MAGIC. - - Which has no checksum or other protection (beyond the size check and the check of the first byte). - - The EC, S/MIME and RFC3161 versons have thier own processors. - -*/ - - -UpdateProcessor::secure_update_processor_err_t UpdateProcessorLegacy::process_header(uint32_t *command, uint8_t* buffer, size_t *len) { - if ((*command) == U_SPIFFS) - return UpdateProcessor::secure_update_processor_OK; - - if ((*command) == U_FLASH) { - if (buffer[0] == ESP_IMAGE_HEADER_MAGIC) { - log_d("Valid magic at start of flash header"); - return UpdateProcessor::secure_update_processor_OK; - }; - log_e("Missing ESP_IMAGE_HEADER_MAGIC"); - }; - log_e("Invalid command 0x%04x ", *command); - - return UpdateProcessor::secure_update_processor_ERROR; -}; +#include "UpdateProcessorWithChecksum.h" + +UpdateProcessorWithChecksum:: UpdateProcessorWithChecksum(UpdateProcessor * nxt) + : _next(nxt) + , _md_info(NULL) + , _md_ctx(NULL) +{ + if (_next == NULL) + _next = new UpdateProcessorLegacy(); +} UpdateProcessorWithChecksum:: ~UpdateProcessorWithChecksum() { if (_md_ctx) mbedtls_md_free(_md_ctx); + if (_next) + delete _next; }; UpdateProcessor::secure_update_processor_err_t UpdateProcessorWithChecksum::setChecksum(const char * crc, mbedtls_md_type_t md_type) { @@ -116,11 +98,11 @@ UpdateProcessor::secure_update_processor_err_t UpdateProcessorWithChecksum::proc log_e("Failed to update digest."); return secure_update_processor_ERROR; } - return secure_update_processor_OK; + return _next->process_payload(buffer, len); }; UpdateProcessor::secure_update_processor_err_t UpdateProcessorWithChecksum::process_end() { - unsigned char buff[MBEDTLS_MD_MAX_SIZE], digest[MBEDTLS_MD_MAX_SIZE]; + unsigned char buff[MBEDTLS_MD_MAX_SIZE]; if (mbedtls_md_finish(_md_ctx, buff)) { log_e("Failed to finalize digest."); @@ -146,7 +128,7 @@ UpdateProcessor::secure_update_processor_err_t UpdateProcessorWithChecksum::proc return UpdateProcessor::secure_update_processor_ERROR; } log_d("Payload digest matches."); - return secure_update_processor_OK; + return _next->process_end(); } diff --git a/libraries/Update/src/UpdateProcessorWithChecksum.h b/libraries/Update/src/UpdateProcessorWithChecksum.h new file mode 100644 index 00000000000..71c472b17ee --- /dev/null +++ b/libraries/Update/src/UpdateProcessorWithChecksum.h @@ -0,0 +1,27 @@ +#ifndef _UPDATE_PROCESSOR_WITH_CHECKSUM +#define _UPDATE_PROCESSOR_WITH_CHECKSUM +#include "UpdateProcessor.h" +#include "UpdateProcessorLegacy.h" + +class UpdateProcessorWithChecksum : public UpdateProcessor { + public: + UpdateProcessorWithChecksum(UpdateProcessor * nxt = NULL); + ~UpdateProcessorWithChecksum(); + + void reset(); + + secure_update_processor_err_t setChecksum(const char * crc, mbedtls_md_type_t md_type = MBEDTLS_MD_NONE); + + secure_update_processor_err_t process_header(uint32_t *command, uint8_t * buffer, size_t *len) { + return secure_update_processor_OK; + }; + + secure_update_processor_err_t process_payload(uint8_t * buff, size_t *len); + secure_update_processor_err_t process_end(); + private: + UpdateProcessor * _next; + const mbedtls_md_info_t *_md_info; + mbedtls_md_context_t * _md_ctx; + unsigned char _md[MBEDTLS_MD_MAX_SIZE]; +}; +#endif diff --git a/libraries/Update/src/Updater.cpp b/libraries/Update/src/Updater.cpp index 8c47982a52c..c036cabdec4 100644 --- a/libraries/Update/src/Updater.cpp +++ b/libraries/Update/src/Updater.cpp @@ -1,9 +1,11 @@ -#include "Update.h" #include "Arduino.h" #include "esp_spi_flash.h" #include "esp_ota_ops.h" #include "esp_image_format.h" +#include "Update.h" +#include "UpdateProcessorLegacy.h" + static const char * _err2str(uint8_t _error) { if (_error == UPDATE_ERROR_OK) { return ("No Error"); @@ -71,7 +73,6 @@ static bool _enablePartition(const esp_partition_t* partition) { } - UpdateClass::UpdateClass(UpdateProcessor * processor) : _processor(0) , _error(0) diff --git a/tools/digital-signing.md b/tools/digital-signing.md index 12f1d827337..5cf558ec236 100644 --- a/tools/digital-signing.md +++ b/tools/digital-signing.md @@ -67,7 +67,7 @@ while you watch the serial output. ## B) OK - so how do I this secure, without any server noncense ? So above example is just that; with no real security. Again -takethe example 'SecureOTA' and open it. +take the example 'SecureOTA' and open it. The next step would be to generate a public/private key; by doing @@ -124,7 +124,7 @@ you have a hierarchy of: | +---- production | | - | +--- product 1 + | +--- plant 1 | +---- engineering | @@ -159,6 +159,10 @@ in an HSM are common choises), have important keys, such as those for production, well contained in a time-server. Yet easily accessible for those who need regular access. +## Note + +If you use RSA keys - they need to be at least 2048 bits long. MBED TLS +sensibly will reject anything shorter. ## Example output (Method A) From ce2b6410bb3b36ff6242c1df15901974e9b5d04a Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Sat, 25 Jul 2020 20:18:54 +0200 Subject: [PATCH 21/24] Fix CMakeList.txt --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2dc32a7cc11..0112009d9d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,7 @@ set(LIBRARY_SRCS libraries/Ticker/src/Ticker.cpp libraries/Update/src/Updater.cpp libraries/Update/src/UpdateProcessor.cpp + libraries/Update/src/UpdateProcessorLegacy.cpp libraries/Update/src/UpdateProcessorRFC3161.cpp libraries/Update/src/mbedtls-ts-addons/signer_info.cpp libraries/Update/src/mbedtls-ts-addons/x509_ts_utils.cpp From 060c4c66518666fd09a19b6dae8a611f58418e5b Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Sat, 25 Jul 2020 21:07:55 +0200 Subject: [PATCH 22/24] Fix CMakeList - file renamed to legacy. --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0112009d9d7..f2baf414661 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,7 +60,6 @@ set(LIBRARY_SRCS libraries/SPI/src/SPI.cpp libraries/Ticker/src/Ticker.cpp libraries/Update/src/Updater.cpp - libraries/Update/src/UpdateProcessor.cpp libraries/Update/src/UpdateProcessorLegacy.cpp libraries/Update/src/UpdateProcessorRFC3161.cpp libraries/Update/src/mbedtls-ts-addons/signer_info.cpp From 60edc5614fa92a1bdb7a6f7edddc117be59c880f Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Sat, 25 Jul 2020 21:46:21 +0200 Subject: [PATCH 23/24] Fix CMakeList - file renamed to legacy 2. --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index f2baf414661..bd8b478ec39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,6 +62,7 @@ set(LIBRARY_SRCS libraries/Update/src/Updater.cpp libraries/Update/src/UpdateProcessorLegacy.cpp libraries/Update/src/UpdateProcessorRFC3161.cpp + libraries/Update/src/UpdateProcessorWithChecksum.cpp libraries/Update/src/mbedtls-ts-addons/signer_info.cpp libraries/Update/src/mbedtls-ts-addons/x509_ts_utils.cpp libraries/Update/src/mbedtls-ts-addons/x509_crt_utils.cpp From 0e54ded4366d99b018efb9c628b9409df1f0537b Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Sun, 26 Jul 2020 12:26:52 +0200 Subject: [PATCH 24/24] Fix SD & HTTP upload builds; include new processer, fix erorr message. --- libraries/HTTPUpdate/src/HTTPUpdate.cpp | 2 +- libraries/HTTPUpdate/src/HTTPUpdate.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/HTTPUpdate/src/HTTPUpdate.cpp b/libraries/HTTPUpdate/src/HTTPUpdate.cpp index d49964b71e2..2c04172fca8 100644 --- a/libraries/HTTPUpdate/src/HTTPUpdate.cpp +++ b/libraries/HTTPUpdate/src/HTTPUpdate.cpp @@ -399,7 +399,7 @@ bool HTTPUpdate::runUpdate(Stream& in, uint32_t size, String crc, int command) if(md_type == MBEDTLS_MD_NONE || _processor->setChecksum(crc.c_str(), md_type)) { _lastError = HTTP_UE_SERVER_FAULTY_MD5; - log_e("Updater::runUpdate failed - cannot handle this checksum (%s, only MD5, SHA1,224,256 and 512 supported as hex string)\n", md5.c_str()); + log_e("Updater::runUpdate failed - cannot handle %s (only MD5, SHA1,224,256 and 512 supported as hex string)", crc.c_str()); return false; }; diff --git a/libraries/HTTPUpdate/src/HTTPUpdate.h b/libraries/HTTPUpdate/src/HTTPUpdate.h index dac29a3b607..a4405b99816 100644 --- a/libraries/HTTPUpdate/src/HTTPUpdate.h +++ b/libraries/HTTPUpdate/src/HTTPUpdate.h @@ -32,6 +32,7 @@ #include #include #include +#include #include