-
Notifications
You must be signed in to change notification settings - Fork 13.3k
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
Changes from 8 commits
305d060
e9a5c48
246edd3
c130c17
617206d
039f4db
e6578b4
b3fe814
c6e4efc
7e2d1fa
a8637bd
564019c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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> | ||
|
@@ -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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @igrr you think maybe we should not reboot if Update failed? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
@@ -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"); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. 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... |
||
}; | ||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Delay not needed here
There was a problem hiding this comment.
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