Skip to content

make HTTP Update Server more secure #2104

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

Merged
merged 12 commits into from
Jun 7, 2016
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
To upload through terminal you can use: curl -F "[email protected]" esp8266-webupdate.local/update
*/

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <ESP8266HTTPUpdateServer.h>

const char* host = "esp8266-webupdate";
const char* update_path = "/firmware";
const char* update_username = "admin";
const char* update_password = "admin";
const char* ssid = "........";
const char* password = "........";

ESP8266WebServer httpServer(80);
ESP8266HTTPUpdateServer httpUpdater;

void setup(void){

Serial.begin(115200);
Serial.println();
Serial.println("Booting Sketch...");
WiFi.mode(WIFI_AP_STA);
WiFi.begin(ssid, password);

while(WiFi.waitForConnectResult() != WL_CONNECTED){
WiFi.begin(ssid, password);
Serial.println("WiFi failed, retrying.");
}

MDNS.begin(host);

httpUpdater.setup(&httpServer, update_path, update_username, update_password);
httpServer.begin();

MDNS.addService("http", "tcp", 80);
Serial.printf("HTTPUpdateServer ready! Open http://%s.local%s in your browser and login with username '%s' and password '%s'\n", host, update_path, update_username, update_password);
}

void loop(void){
httpServer.handleClient();
delay(1);
Copy link
Member

Choose a reason for hiding this comment

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

Delay not needed here

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah... was in the other example, so I left it there thinking might be needed. I'll remove it from both

}
37 changes: 25 additions & 12 deletions libraries/ESP8266HTTPUpdateServer/src/ESP8266HTTPUpdateServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@


const char* ESP8266HTTPUpdateServer::_serverIndex =
R"(<html><body><form method='POST' action='/update' enctype='multipart/form-data'>
R"(<html><body><form method='POST' action='' enctype='multipart/form-data'>
<input type='file' name='update'>
<input type='submit' value='Update'>
</form>
Expand All @@ -17,24 +17,29 @@ ESP8266HTTPUpdateServer::ESP8266HTTPUpdateServer(bool serial_debug)
{
_serial_output = serial_debug;
_server = NULL;
_username = NULL;
_password = NULL;
_authenticated = false;
}

void ESP8266HTTPUpdateServer::setup(ESP8266WebServer *server)
void ESP8266HTTPUpdateServer::setup(ESP8266WebServer *server, const char * path, const char * username, const char * password)
{
_server = server;
_username = (char *)username;
_password = (char *)password;

// handler for the /update form page
_server->on("/update", HTTP_GET, [&](){
_server->sendHeader("Connection", "close");
_server->sendHeader("Access-Control-Allow-Origin", "*");
_server->on(path, HTTP_GET, [&](){
if(_username != NULL && _password != NULL && !_server->authenticate(_username, _password))
return _server->requestAuthentication();
_server->send(200, "text/html", _serverIndex);
});

// handler for the /update form POST (once file upload finishes)
_server->on("/update", HTTP_POST, [&](){
_server->sendHeader("Connection", "close");
_server->sendHeader("Access-Control-Allow-Origin", "*");
_server->send(200, "text/html", (Update.hasError())?"FAIL":"<META http-equiv=\"refresh\" content=\"15;URL=/update\">OK");
_server->on(path, HTTP_POST, [&](){
if(!_authenticated)
return _server->requestAuthentication();
_server->send(200, "text/html", (Update.hasError())?"FAIL":"<META http-equiv=\"refresh\" content=\"15;URL=\">OK");
ESP.restart();
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@igrr you think maybe we should not reboot if Update failed?

Copy link
Member

Choose a reason for hiding this comment

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

I'll merge this as is, let's address this in a separate pull request. I think a good idea would be to add update callbacks similar to ESP8266HTTPUpdate (client). Actually maybe we need to review APIs of HTTP update client and HTTP update server and make them more uniform. Let's discuss further on gitter.

},[&](){
// handler for the file upload, get's the sketch bytes, and writes
Expand All @@ -43,27 +48,35 @@ void ESP8266HTTPUpdateServer::setup(ESP8266WebServer *server)
if(upload.status == UPLOAD_FILE_START){
if (_serial_output)
Serial.setDebugOutput(true);

_authenticated = (_username == NULL || _password == NULL || _server->authenticate(_username, _password));
if(!_authenticated){
if (_serial_output)
Serial.printf("Unauthenticated Update\n");
return;
}

WiFiUDP::stopAll();
if (_serial_output)
Serial.printf("Update: %s\n", upload.filename.c_str());
uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
if(!Update.begin(maxSketchSpace)){//start with max available size
if (_serial_output) Update.printError(Serial);
}
} else if(upload.status == UPLOAD_FILE_WRITE){
} else if(_authenticated && upload.status == UPLOAD_FILE_WRITE){
if (_serial_output) Serial.printf(".");
if(Update.write(upload.buf, upload.currentSize) != upload.currentSize){
if (_serial_output) Update.printError(Serial);

}
} else if(upload.status == UPLOAD_FILE_END){
} else if(_authenticated && upload.status == UPLOAD_FILE_END){
if(Update.end(true)){ //true to set the size to the current progress
if (_serial_output) Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
} else {
if (_serial_output) Update.printError(Serial);
}
if (_serial_output) Serial.setDebugOutput(false);
} else if(upload.status == UPLOAD_FILE_ABORTED){
} else if(_authenticated && upload.status == UPLOAD_FILE_ABORTED){
Update.end();
if (_serial_output) Serial.println("Update was aborted");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ class ESP8266HTTPUpdateServer
bool _serial_output;
ESP8266WebServer *_server;
static const char *_serverIndex;
char * _username;
char * _password;
bool _authenticated;
public:
ESP8266HTTPUpdateServer(bool serial_debug=false);
void setup(ESP8266WebServer *server=NULL);
void setup(ESP8266WebServer *server, const char * path="/update", const char * username=NULL, const char * password=NULL);
Copy link
Member

Choose a reason for hiding this comment

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

Let's use a pair of overloaded functions instead of a function with bunch of default arguments here.
I.e.

void setup(ESP8266WebServer *server); // used to keep code backward compatible, calls setup(server, "/update", NULL, NULL)
void setup(ESP8266WebServer *server, const char * path); // optional, add this if you want... not really necessary IMO. calls setup(server, path, NULL, NULL);
void setup(ESP8266WebServer *server, const char * path, const char* username, const char* password); // new overload, all arguments are required

Reason for such a design is that it's easier to refactor/extend/deprecate an interface based on overloaded functions compared to an interface which uses function with many default arguments. This was very obvious with ESP8266HTTPClient and ESP8266HTTPUpdate, which used default arguments heavily. It was really painful to separate functions which needed TLS from the ones which didn't...

};


Expand Down