Skip to content

Pass Client parameter to httpUpdate to use any BearSSL security method for a firmware update (with example) #4832

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 24 commits into from
Closed

Pass Client parameter to httpUpdate to use any BearSSL security method for a firmware update (with example) #4832

wants to merge 24 commits into from

Conversation

Jeroen88
Copy link
Contributor

@Jeroen88 Jeroen88 commented Jun 20, 2018

Added two new update() and one updateSpiffs() function that uses the ESP8266HTTPClient adapted by me in PR #4825. With these adaptations, it is possible to do an encrypted and authenticated server firmware or SPIFFS update, using any of the supported security options of the WiFiClientSecureBearSSL library. httpUpdateSecure.ino is a complete example.

To try this, you need either a sketch with enough free memory to allocate the full TLS/SSL-buffer (16k), or a, currently not very widely supported Maximum Fragment Length Negotiation enabled, server (or do the same trick as I do, using a nodeJS server and set maxSendFragmentLength) that sends the correct response headers and serves the binary. You need a certificate store in SPIFFs that includes your root CA, or a self signed certificate, or a certificate fingerprint. In the latter two cases you need to edit the example to use allowSelfSignedCerts() or setFingerprint(). It is also possible to use encryption without server authentication. In this case use setInsecure(). Of course any of the other supported WiFiClientSecureBearSSL functions may be used as well.

I have a question: in ESP8266httpUpdate.cpp with these new functions, a Client& is passed into the update() functions. After the headers have been checked in handleUpdate(), a WiFiClient * is retrieved from the http client :

WiFiClient * tcp = (WiFiClient *) http.getStreamPtr();

This works fine, however: in PR #4825 I designed httpClient as a generic client for any Client. There is no fundamental reason not to use e.g. EthernetClient. But in handleUpdate() a WiFiClient * is necessary to do the actual update. Is it possible to use a base class Client instead?

@Jeroen88
Copy link
Contributor Author

Travis build failed because of macro redefinitions not within my scope, e.g.:
In file included from common/Arduino.h:241:0,
from ../../cores/esp8266/StreamString.cpp:23:
../../cores/esp8266/WCharacter.h:24:0: warning: "isascii" redefined [enabled by default]
#define isascii(__c) ((unsigned)(__c)<=0177)
^

@Jeroen88
Copy link
Contributor Author

Travis build passed :)

@Jeroen88 Jeroen88 changed the title Feature/httpupdate client parameter Pass Client parameter to httpUpdate to use any BearSSL security method for a firmware update (with example) Jun 21, 2018
@earlephilhower earlephilhower self-requested a review July 1, 2018 21:29
@earlephilhower earlephilhower added this to the 2.5.0 milestone Jul 6, 2018
Copy link
Collaborator

@earlephilhower earlephilhower left a comment

Choose a reason for hiding this comment

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

Very nice work! Some concerns about types and the new example, but nothing major. Shouldn't be an issue getting it into 2.5.0.

HTTPClient http;

USE_SERIAL.print("[HTTP] begin...\n");
// configure traged server and url
//http.begin("https://192.168.1.12/test.html", "7a 9c f4 db 40 d3 62 5a 6e 21 bc 5c cc 66 c8 3e a1 45 59 38"); //HTTPS
http.begin("http://192.168.1.12/test.html"); //HTTP
if (http.begin((Client &)client, "http://tls.mbed.org/")) { // HTTP
Copy link
Collaborator

Choose a reason for hiding this comment

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

Any reason this one picked? Isn't tls.mbed.org https-only?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also, thanks for checking return codes!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

HTTPClient http;

USE_SERIAL.print("[HTTP] begin...\n");
// configure traged server and url


http.begin("http://user:[email protected]/test.html");
http.begin((Client&)client, "http://guest:[email protected]/HTTP/Basic/");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Isn't there no need for the cast (Client&) here and in the other examples? Your prototype will make the C++ compiler pass the pointer no matter what, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You will be right. I'll check it and then change it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The cast has been removed

@@ -65,7 +67,7 @@ void loop() {
uint8_t buff[128] = { 0 };

// get tcp stream
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we get rid of the stream temp variable completely since it's no longer needed with your fix? i.e. there is no getStreamPtr() needed anymore?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Actually, I see later on that you still have a "getStreamPtr()" method with the new one, too. Any reason not to just keep using it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I do not quite understand you here.
The getStreamPtr() is embedded in the #ifdef KEEP_PRESENT_API, so it's just there for backward compatibility.
I added the streaming example on purpose, because a server response may be too large to fit into a String, making it impossible to use getString(), but still possible to stream it. Moreover, since it is String getString() instead of String &getString() the response might even be copied (allthough the compiler may optimize this, I don't know)
Could you explain what you are suggesting?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I understand you now. The getStreamPtr() is kept as part of the existing API, however in the examples the unneeded call to getStreamPtr() has been removed.

uint8_t buff[128] = { 0 };

// get tcp stream
WiFiClient * stream = &client;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same as above, no more "get tcp stream" with your change.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

getStreamPtr() has been removed from the example

}

_port = (protocol == "https" ? 443 : 80);
return beginInternal(url, protocol.c_str());
Copy link
Collaborator

Choose a reason for hiding this comment

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

There shouldn't be any need for the protocol anymore, no? The WiFiCilent* passed in should already either be encrypted or not, and the code paths for both are the same, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes it is still needed. The ESP8266HTTPClient is setting up (and maintaining with Keep-Alive) the connection, and it should know what port to use. The idea (also in the original version) of this begin() method , is to pass all the needed information to set up the connection through a fully qualified URL.

_host = host;
_port = port;
_uri = uri;
_protocol = (https ? "https" : "http");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same question as above about the protocol, is it needed anymore?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The _port is needed, see in connect(): if(!_tcp->connect(_host.c_str(), _port)) { ...
If you mean that that _protocol can be dropped as class variable, I think you are right. I'll check that and wrap it into a #ifdef KEEP_PRESENT_API if possible

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The _protocol may be dropped in the new class, but beginInternal() is still used, and this function uses _protocol as well. In this function _protocol could be changed into a variable local to that function. I did not do this to stay as close as possible to the original code.

_tcp->setNoDelay(true);
#ifdef KEEP_PRESENT_API
if(_tcpDeprecated)
_tcpDeprecated->setNoDelay(true);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Wouldn't you want to set it on _tcp as well (outside this #define, of course)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Client::setNoDelay() does not exist. If I change Client in WiFiClient its is possible, see my remark later

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now setNoDelay() is alway called

#include <WiFiClient.h>
#else
#include <Client.h>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why move to abstract Client from WiFiClient? I don't remember ever seeing anything else take this root class.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The idea was that this class could be used for other Clients as well, eg an EthernetClient as defined here (https://github.com/esp8266/Arduino/blob/master/libraries/Ethernet/src/EthernetClient.h). But because most people will use the WiFiClient, I will change Client to WiFiClient

@@ -0,0 +1,138 @@
/**
Copy link
Collaborator

Choose a reason for hiding this comment

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

While I'm happy to see someone using CertStore, for ESP8266 updates that's probably not what we want people to do since it's saying you'd accept an update from anyone on the internet.

What most people would want to do is have a single, private certificate that they generate and keep secure on their own company's system. It would make this new example shorter and simpler, too, and focus it on the new way of doing the update vs. the overhead for making a CertStore...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I do not understand:
You will not accept an update from anyone on the internet because the URL determines where you fetch the update, and the Client identifies the host. Also, there is no need to populate the cert store with all root certificates, just your company's one is possible too.
Do you want an extra, simpler example just using a cert? Could you provide it?

@@ -276,7 +304,7 @@ HTTPUpdateResult ESP8266HTTPUpdate::handleUpdate(HTTPClient& http, const String&
ret = HTTP_UPDATE_FAILED;
} else {

WiFiClient * tcp = http.getStreamPtr();
WiFiClient * tcp = (WiFiClient *) http.getStreamPtr();
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm fine with casts like this, but I think @devyte will probably ask for static_cast<>. No change from me, just a warning...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will try the static_cast

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because http.getStreamPtr() returns a WiFiClient now, casting is no longer necessary

@Jeroen88
Copy link
Contributor Author

@earlephilhower thnx for the review! I will make updates and give answers to your questions / remarks soon!

@Jeroen88
Copy link
Contributor Author

Requested review changes are made and posted as a new PR #4980.

@Jeroen88 Jeroen88 closed this Jul 29, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants