From a4020cce69d464b4f596b255417d67b46313d1c6 Mon Sep 17 00:00:00 2001 From: Lan Hekary Date: Wed, 25 Jan 2017 03:52:26 +0200 Subject: [PATCH 1/7] Add Digest Auth --- .../ESP8266WebServer/src/ESP8266WebServer.cpp | 88 ++++++++++++++++++- .../ESP8266WebServer/src/ESP8266WebServer.h | 9 +- 2 files changed, 92 insertions(+), 5 deletions(-) diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp b/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp index bc41a5603b..657895909b 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp @@ -94,6 +94,12 @@ void ESP8266WebServer::begin() { collectHeaders(0, 0); } +String ESP8266WebServer::_exractparam(String& authReq,const String& param,const char delimit){ + int _begin = authReq.indexOf(param); + if (_begin==-1) return ""; + return authReq.substring(_begin+param.length(),authReq.indexOf(delimit,_begin+param.length())); +} + bool ESP8266WebServer::authenticate(const char * username, const char * password){ if(hasHeader(AUTHORIZATION_HEADER)){ String authReq = header(AUTHORIZATION_HEADER); @@ -121,15 +127,89 @@ bool ESP8266WebServer::authenticate(const char * username, const char * password } delete[] toencode; delete[] encoded; - } + }else if(authReq.startsWith("Digest")){ + authReq = authReq.substring(7); + #ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.println(authReq); + #endif + String _username = _exractparam(authReq,"username=\""); + if((!_username.length())||_username!=String(username)){ + authReq = String(); + return false; + } + // extracting required parameters for RFC 2069 simpler Digest + String _realm = _exractparam(authReq,"realm=\""); + String _nonce = _exractparam(authReq,"nonce=\""); + String _uri = _exractparam(authReq,"uri=\""); + String _response = _exractparam(authReq,"response=\""); + String _opaque = _exractparam(authReq,"opaque=\""); + + if((!_realm.length())||(!_nonce.length())||(!_uri.length())||(!_response.length())||(!_opaque.length())){ + authReq = String(); + return false; + } + // parameters for the RFC 2617 newer Digest + String _nc,_cnonce; + if(authReq.indexOf("qop=auth") != -1){ + _nc = _exractparam(authReq,"nc=",','); + _cnonce = _exractparam(authReq,"cnonce=\""); + } + MD5Builder md5; + md5.begin(); + md5.add(String(username)+":"+_realm+":"+String(password)); // md5 of the user:realm:user + md5.calculate(); + String _H1 = md5.toString(); + #ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.println("Hash of user:realm:pass=" + _H1); + #endif + md5.begin(); + if(_currentMethod == HTTP_GET){ + md5.add("GET:"+_uri); + }else if(_currentMethod == HTTP_POST){ + md5.add("POST:"+_uri); + }else if(_currentMethod == HTTP_PUT){ + md5.add("PUT:"+_uri); + }else if(_currentMethod == HTTP_DELETE){ + md5.add("DELETE:"+_uri); + }else{ + md5.add("GET:"+_uri); + } + md5.calculate(); + String _H2 = md5.toString(); + #ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.println("Hash of GET:uri=" + _H2); + #endif + md5.begin(); + if(authReq.indexOf("qop=auth") != -1){ + md5.add(_H1+":"+_nonce+":"+_nc+":"+_cnonce+":auth:"+_H2); + }else{ + md5.add(_H1+":"+_nonce+":"+_H2); + } + md5.calculate(); + String _responsecheck = md5.toString(); + #ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.println("The Proper response=" +_responsecheck); + #endif + Serial.println(_responsecheck); + if(_response==_responsecheck){ + authReq = String(); + return true; + } + } authReq = String(); } return false; } -void ESP8266WebServer::requestAuthentication(){ - sendHeader("WWW-Authenticate", "Basic realm=\"Login Required\""); - send(401); +void ESP8266WebServer::requestAuthentication(HTTPAuthMethod mode){ + if(mode==BASIC_AUTH){ + sendHeader("WWW-Authenticate", "Basic realm=\"Login Required\""); + }else{ + _snonce=String(RANDOM_REG32,HEX)+String(RANDOM_REG32,HEX)+String(RANDOM_REG32,HEX)+String(RANDOM_REG32,HEX); + _sopaque=String(RANDOM_REG32,HEX)+String(RANDOM_REG32,HEX)+String(RANDOM_REG32,HEX)+String(RANDOM_REG32,HEX); + sendHeader("WWW-Authenticate", "Digest realm=\"Login Required\", qop=\"auth\", nonce=\""+_snonce+"\", opaque=\""+_sopaque+"\""); + } + send(401,"text/html","Authentication Failed"); } void ESP8266WebServer::on(const String &uri, ESP8266WebServer::THandlerFunction handler) { diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer.h b/libraries/ESP8266WebServer/src/ESP8266WebServer.h index 8e0cf0cf4f..2dba9b7d7b 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer.h +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer.h @@ -31,6 +31,7 @@ enum HTTPMethod { HTTP_ANY, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELE enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END, UPLOAD_FILE_ABORTED }; enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE }; +enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH }; #define HTTP_DOWNLOAD_UNIT_SIZE 1460 #define HTTP_UPLOAD_BUFLEN 2048 @@ -74,7 +75,7 @@ class ESP8266WebServer void stop(); bool authenticate(const char * username, const char * password); - void requestAuthentication(); + void requestAuthentication(HTTPAuthMethod mode = DIGEST_AUTH); typedef std::function THandlerFunction; void on(const String &uri, THandlerFunction handler); @@ -146,6 +147,9 @@ template size_t streamFile(T &file, const String& contentType){ void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength); bool _collectHeader(const char* headerName, const char* headerValue); + // for extracting Auth parameters + String _exractparam(String& authReq,const String& param,const char delimit = '"'); + struct RequestArgument { String key; String value; @@ -178,6 +182,9 @@ template size_t streamFile(T &file, const String& contentType){ String _hostHeader; bool _chunked; + String _snonce; // Store noance for future comparison + String _sopaque; + }; From 9d7397a2fd6c019b4fc2fdf884d92c3a4949373d Mon Sep 17 00:00:00 2001 From: Lan Hekary Date: Wed, 25 Jan 2017 04:17:54 +0200 Subject: [PATCH 2/7] Check for Opaque and Nonce --- libraries/ESP8266WebServer/src/ESP8266WebServer.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp b/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp index 657895909b..3a6c989f6c 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp @@ -143,11 +143,15 @@ bool ESP8266WebServer::authenticate(const char * username, const char * password String _uri = _exractparam(authReq,"uri=\""); String _response = _exractparam(authReq,"response=\""); String _opaque = _exractparam(authReq,"opaque=\""); - + if((!_realm.length())||(!_nonce.length())||(!_uri.length())||(!_response.length())||(!_opaque.length())){ authReq = String(); return false; } + if((_opaque!=_sopaque)||(_nonce!=_snonce)){ + authReq = String(); + return false; + } // parameters for the RFC 2617 newer Digest String _nc,_cnonce; if(authReq.indexOf("qop=auth") != -1){ From e8c35d9ba2fd3fc981181d3c0a8cb285a0e5a75b Mon Sep 17 00:00:00 2001 From: Lan Hekary Date: Wed, 25 Jan 2017 04:54:56 +0200 Subject: [PATCH 3/7] Remove Serial Debug and fix Indentation --- .../ESP8266WebServer/src/ESP8266WebServer.cpp | 137 +++++++++--------- 1 file changed, 68 insertions(+), 69 deletions(-) diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp b/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp index 3a6c989f6c..f9e4c1816c 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp @@ -128,77 +128,76 @@ bool ESP8266WebServer::authenticate(const char * username, const char * password delete[] toencode; delete[] encoded; }else if(authReq.startsWith("Digest")){ - authReq = authReq.substring(7); - #ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.println(authReq); - #endif - String _username = _exractparam(authReq,"username=\""); - if((!_username.length())||_username!=String(username)){ - authReq = String(); - return false; - } - // extracting required parameters for RFC 2069 simpler Digest - String _realm = _exractparam(authReq,"realm=\""); - String _nonce = _exractparam(authReq,"nonce=\""); - String _uri = _exractparam(authReq,"uri=\""); - String _response = _exractparam(authReq,"response=\""); - String _opaque = _exractparam(authReq,"opaque=\""); - - if((!_realm.length())||(!_nonce.length())||(!_uri.length())||(!_response.length())||(!_opaque.length())){ - authReq = String(); - return false; - } + authReq = authReq.substring(7); + #ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.println(authReq); + #endif + String _username = _exractparam(authReq,"username=\""); + if((!_username.length())||_username!=String(username)){ + authReq = String(); + return false; + } + // extracting required parameters for RFC 2069 simpler Digest + String _realm = _exractparam(authReq,"realm=\""); + String _nonce = _exractparam(authReq,"nonce=\""); + String _uri = _exractparam(authReq,"uri=\""); + String _response = _exractparam(authReq,"response=\""); + String _opaque = _exractparam(authReq,"opaque=\""); + + if((!_realm.length())||(!_nonce.length())||(!_uri.length())||(!_response.length())||(!_opaque.length())){ + authReq = String(); + return false; + } if((_opaque!=_sopaque)||(_nonce!=_snonce)){ - authReq = String(); - return false; + authReq = String(); + return false; + } + // parameters for the RFC 2617 newer Digest + String _nc,_cnonce; + if(authReq.indexOf("qop=auth") != -1){ + _nc = _exractparam(authReq,"nc=",','); + _cnonce = _exractparam(authReq,"cnonce=\""); + } + MD5Builder md5; + md5.begin(); + md5.add(String(username)+":"+_realm+":"+String(password)); // md5 of the user:realm:user + md5.calculate(); + String _H1 = md5.toString(); + #ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.println("Hash of user:realm:pass=" + _H1); + #endif + md5.begin(); + if(_currentMethod == HTTP_GET){ + md5.add("GET:"+_uri); + }else if(_currentMethod == HTTP_POST){ + md5.add("POST:"+_uri); + }else if(_currentMethod == HTTP_PUT){ + md5.add("PUT:"+_uri); + }else if(_currentMethod == HTTP_DELETE){ + md5.add("DELETE:"+_uri); + }else{ + md5.add("GET:"+_uri); + } + md5.calculate(); + String _H2 = md5.toString(); + #ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.println("Hash of GET:uri=" + _H2); + #endif + md5.begin(); + if(authReq.indexOf("qop=auth") != -1){ + md5.add(_H1+":"+_nonce+":"+_nc+":"+_cnonce+":auth:"+_H2); + }else{ + md5.add(_H1+":"+_nonce+":"+_H2); + } + md5.calculate(); + String _responsecheck = md5.toString(); + #ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.println("The Proper response=" +_responsecheck); + #endif + if(_response==_responsecheck){ + authReq = String(); + return true; } - // parameters for the RFC 2617 newer Digest - String _nc,_cnonce; - if(authReq.indexOf("qop=auth") != -1){ - _nc = _exractparam(authReq,"nc=",','); - _cnonce = _exractparam(authReq,"cnonce=\""); - } - MD5Builder md5; - md5.begin(); - md5.add(String(username)+":"+_realm+":"+String(password)); // md5 of the user:realm:user - md5.calculate(); - String _H1 = md5.toString(); - #ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.println("Hash of user:realm:pass=" + _H1); - #endif - md5.begin(); - if(_currentMethod == HTTP_GET){ - md5.add("GET:"+_uri); - }else if(_currentMethod == HTTP_POST){ - md5.add("POST:"+_uri); - }else if(_currentMethod == HTTP_PUT){ - md5.add("PUT:"+_uri); - }else if(_currentMethod == HTTP_DELETE){ - md5.add("DELETE:"+_uri); - }else{ - md5.add("GET:"+_uri); - } - md5.calculate(); - String _H2 = md5.toString(); - #ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.println("Hash of GET:uri=" + _H2); - #endif - md5.begin(); - if(authReq.indexOf("qop=auth") != -1){ - md5.add(_H1+":"+_nonce+":"+_nc+":"+_cnonce+":auth:"+_H2); - }else{ - md5.add(_H1+":"+_nonce+":"+_H2); - } - md5.calculate(); - String _responsecheck = md5.toString(); - #ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.println("The Proper response=" +_responsecheck); - #endif - Serial.println(_responsecheck); - if(_response==_responsecheck){ - authReq = String(); - return true; - } } authReq = String(); } From 28f572b97f9364b8db17e182e35458763606c676 Mon Sep 17 00:00:00 2001 From: Lan Hekary Date: Wed, 1 Feb 2017 08:22:21 +0200 Subject: [PATCH 4/7] Added example sketch with documentation,Fixed indentation and Defaults --- .gitignore | 1 + .../HttpAdvancedAuth/HttpAdvancedAuth.ino | 45 +++++ .../ESP8266WebServer/src/ESP8266WebServer.cpp | 159 +++++++++--------- .../ESP8266WebServer/src/ESP8266WebServer.h | 5 +- 4 files changed, 131 insertions(+), 79 deletions(-) create mode 100644 libraries/ESP8266WebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino diff --git a/.gitignore b/.gitignore index c36bdd3aa3..7d74b8bcff 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ tools/sdk/lwip/src/build tools/sdk/lwip/src/liblwip_src.a *.pyc +*.gch diff --git a/libraries/ESP8266WebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino b/libraries/ESP8266WebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino new file mode 100644 index 0000000000..2448509d9f --- /dev/null +++ b/libraries/ESP8266WebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino @@ -0,0 +1,45 @@ +#include +#include +#include +#include + +const char* ssid = "........"; +const char* password = "........"; + +ESP8266WebServer server(80); + +const char* www_username = "admin"; +const char* www_password = "esp8266"; +const char* www_realm = "Custom Realm"; // allows you to set the realm of authentication Default:"Login Required" +String AuthFailResponse = "Authentication Failed"; // the Content of the HTML response in case of Unautherized Access Default:empty + +void setup() { + Serial.begin(115200); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + if(WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.println("WiFi Connect Failed! Rebooting..."); + delay(1000); + ESP.restart(); + } + ArduinoOTA.begin(); + + server.on("/", [](){ + if(!server.authenticate(www_username, www_password)) + //return server.requestAuthentication(BASIC_AUTH, www_realm, AuthFailResponse); //Basic Auth Method with Custom realm and Failure Response + //return server.requestAuthentication(DIGEST_AUTH); //Digest Auth Method with realm="Login Required" and empty Failure Response + //return server.requestAuthentication(DIGEST_AUTH, www_realm); //Digest Auth Method with Custom realm and empty Failure Response + return server.requestAuthentication(DIGEST_AUTH, www_realm, AuthFailResponse); //Digest Auth Method with Custom realm and Failure Response + server.send(200, "text/plain", "Login OK"); + }); + server.begin(); + + Serial.print("Open http://"); + Serial.print(WiFi.localIP()); + Serial.println("/ in your browser to see it working"); +} + +void loop() { + ArduinoOTA.handle(); + server.handleClient(); +} diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp b/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp index f9e4c1816c..594c6e8f3f 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp @@ -95,9 +95,9 @@ void ESP8266WebServer::begin() { } String ESP8266WebServer::_exractparam(String& authReq,const String& param,const char delimit){ - int _begin = authReq.indexOf(param); - if (_begin==-1) return ""; - return authReq.substring(_begin+param.length(),authReq.indexOf(delimit,_begin+param.length())); + int _begin = authReq.indexOf(param); + if (_begin==-1) return ""; + return authReq.substring(_begin+param.length(),authReq.indexOf(delimit,_begin+param.length())); } bool ESP8266WebServer::authenticate(const char * username, const char * password){ @@ -128,91 +128,96 @@ bool ESP8266WebServer::authenticate(const char * username, const char * password delete[] toencode; delete[] encoded; }else if(authReq.startsWith("Digest")){ - authReq = authReq.substring(7); - #ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.println(authReq); - #endif - String _username = _exractparam(authReq,"username=\""); - if((!_username.length())||_username!=String(username)){ - authReq = String(); - return false; + authReq = authReq.substring(7); + #ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.println(authReq); + #endif + String _username = _exractparam(authReq,"username=\""); + if((!_username.length())||_username!=String(username)){ + authReq = String(); + return false; + } + // extracting required parameters for RFC 2069 simpler Digest + String _realm = _exractparam(authReq,"realm=\""); + String _nonce = _exractparam(authReq,"nonce=\""); + String _uri = _exractparam(authReq,"uri=\""); + String _response = _exractparam(authReq,"response=\""); + String _opaque = _exractparam(authReq,"opaque=\""); + + if((!_realm.length())||(!_nonce.length())||(!_uri.length())||(!_response.length())||(!_opaque.length())){ + authReq = String(); + return false; } - // extracting required parameters for RFC 2069 simpler Digest - String _realm = _exractparam(authReq,"realm=\""); - String _nonce = _exractparam(authReq,"nonce=\""); - String _uri = _exractparam(authReq,"uri=\""); - String _response = _exractparam(authReq,"response=\""); - String _opaque = _exractparam(authReq,"opaque=\""); - - if((!_realm.length())||(!_nonce.length())||(!_uri.length())||(!_response.length())||(!_opaque.length())){ - authReq = String(); - return false; - } - if((_opaque!=_sopaque)||(_nonce!=_snonce)){ - authReq = String(); - return false; + if((_opaque!=_sopaque)||(_nonce!=_snonce)||(_realm!=_srealm)){ + authReq = String(); + return false; } - // parameters for the RFC 2617 newer Digest - String _nc,_cnonce; - if(authReq.indexOf("qop=auth") != -1){ - _nc = _exractparam(authReq,"nc=",','); - _cnonce = _exractparam(authReq,"cnonce=\""); + // parameters for the RFC 2617 newer Digest + String _nc,_cnonce; + if(authReq.indexOf("qop=auth") != -1){ + _nc = _exractparam(authReq,"nc=",','); + _cnonce = _exractparam(authReq,"cnonce=\""); } - MD5Builder md5; - md5.begin(); - md5.add(String(username)+":"+_realm+":"+String(password)); // md5 of the user:realm:user - md5.calculate(); - String _H1 = md5.toString(); - #ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.println("Hash of user:realm:pass=" + _H1); - #endif - md5.begin(); - if(_currentMethod == HTTP_GET){ - md5.add("GET:"+_uri); - }else if(_currentMethod == HTTP_POST){ - md5.add("POST:"+_uri); - }else if(_currentMethod == HTTP_PUT){ - md5.add("PUT:"+_uri); - }else if(_currentMethod == HTTP_DELETE){ - md5.add("DELETE:"+_uri); - }else{ - md5.add("GET:"+_uri); + MD5Builder md5; + md5.begin(); + md5.add(String(username)+":"+_realm+":"+String(password)); // md5 of the user:realm:user + md5.calculate(); + String _H1 = md5.toString(); + #ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.println("Hash of user:realm:pass=" + _H1); + #endif + md5.begin(); + if(_currentMethod == HTTP_GET){ + md5.add("GET:"+_uri); + }else if(_currentMethod == HTTP_POST){ + md5.add("POST:"+_uri); + }else if(_currentMethod == HTTP_PUT){ + md5.add("PUT:"+_uri); + }else if(_currentMethod == HTTP_DELETE){ + md5.add("DELETE:"+_uri); + }else{ + md5.add("GET:"+_uri); } - md5.calculate(); - String _H2 = md5.toString(); - #ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.println("Hash of GET:uri=" + _H2); - #endif - md5.begin(); - if(authReq.indexOf("qop=auth") != -1){ - md5.add(_H1+":"+_nonce+":"+_nc+":"+_cnonce+":auth:"+_H2); - }else{ - md5.add(_H1+":"+_nonce+":"+_H2); + md5.calculate(); + String _H2 = md5.toString(); + #ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.println("Hash of GET:uri=" + _H2); + #endif + md5.begin(); + if(authReq.indexOf("qop=auth") != -1){ + md5.add(_H1+":"+_nonce+":"+_nc+":"+_cnonce+":auth:"+_H2); + }else{ + md5.add(_H1+":"+_nonce+":"+_H2); } - md5.calculate(); - String _responsecheck = md5.toString(); - #ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.println("The Proper response=" +_responsecheck); - #endif - if(_response==_responsecheck){ - authReq = String(); - return true; + md5.calculate(); + String _responsecheck = md5.toString(); + #ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.println("The Proper response=" +_responsecheck); + #endif + if(_response==_responsecheck){ + authReq = String(); + return true; } - } + } authReq = String(); } return false; } -void ESP8266WebServer::requestAuthentication(HTTPAuthMethod mode){ - if(mode==BASIC_AUTH){ - sendHeader("WWW-Authenticate", "Basic realm=\"Login Required\""); - }else{ - _snonce=String(RANDOM_REG32,HEX)+String(RANDOM_REG32,HEX)+String(RANDOM_REG32,HEX)+String(RANDOM_REG32,HEX); - _sopaque=String(RANDOM_REG32,HEX)+String(RANDOM_REG32,HEX)+String(RANDOM_REG32,HEX)+String(RANDOM_REG32,HEX); - sendHeader("WWW-Authenticate", "Digest realm=\"Login Required\", qop=\"auth\", nonce=\""+_snonce+"\", opaque=\""+_sopaque+"\""); - } - send(401,"text/html","Authentication Failed"); +void ESP8266WebServer::requestAuthentication(HTTPAuthMethod mode, const char* realm, const String& AuthFailMsg){ + if(realm==NULL){ + _srealm = "Login Required"; + }else{ + _srealm = String(realm); + } + if(mode==BASIC_AUTH){ + sendHeader("WWW-Authenticate", "Basic realm=\"" + _srealm + "\""); + }else{ + _snonce=String(RANDOM_REG32,HEX)+String(RANDOM_REG32,HEX)+String(RANDOM_REG32,HEX)+String(RANDOM_REG32,HEX); + _sopaque=String(RANDOM_REG32,HEX)+String(RANDOM_REG32,HEX)+String(RANDOM_REG32,HEX)+String(RANDOM_REG32,HEX); + sendHeader("WWW-Authenticate", "Digest realm=\"" +_srealm + "\", qop=\"auth\", nonce=\""+_snonce+"\", opaque=\""+_sopaque+"\""); + } + send(401,"text/html",AuthFailMsg); } void ESP8266WebServer::on(const String &uri, ESP8266WebServer::THandlerFunction handler) { diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer.h b/libraries/ESP8266WebServer/src/ESP8266WebServer.h index 2dba9b7d7b..026baca68c 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer.h +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer.h @@ -75,7 +75,7 @@ class ESP8266WebServer void stop(); bool authenticate(const char * username, const char * password); - void requestAuthentication(HTTPAuthMethod mode = DIGEST_AUTH); + void requestAuthentication(HTTPAuthMethod mode = BASIC_AUTH, const char* realm = NULL, const String& AuthFailMsg = String("") ); typedef std::function THandlerFunction; void on(const String &uri, THandlerFunction handler); @@ -182,8 +182,9 @@ template size_t streamFile(T &file, const String& contentType){ String _hostHeader; bool _chunked; - String _snonce; // Store noance for future comparison + String _snonce; // Store noance and opaque for future comparison String _sopaque; + String _srealm; // Store the Auth realm between Calls }; From 91d9cc14481cfc569f61ed172149b6de27a59589 Mon Sep 17 00:00:00 2001 From: Ahmed El-Sharnoby Date: Sat, 9 Sep 2017 11:15:23 +0200 Subject: [PATCH 5/7] Digest Authentication minor changes + new padded 32 digit random function --- .../HttpAdvancedAuth/HttpAdvancedAuth.ino | 42 ++++++++++++++++--- .../ESP8266WebServer/src/ESP8266WebServer.cpp | 17 ++++++-- .../ESP8266WebServer/src/ESP8266WebServer.h | 5 ++- 3 files changed, 52 insertions(+), 12 deletions(-) diff --git a/libraries/ESP8266WebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino b/libraries/ESP8266WebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino index 2448509d9f..6f4a50bebb 100644 --- a/libraries/ESP8266WebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino +++ b/libraries/ESP8266WebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino @@ -1,3 +1,27 @@ +/* + Http Advanced Authentication example + Copyright (c) 2017 Ahmed El-Sharnoby . All rights reserved. + This file is part of the ESP8266WebServer library for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Explaining the use of Advanced Authentication Methods to increase Security + Recommended to use the Digest Authentication Method as it's less vulnerable + than the Basic Method +*/ + #include #include #include @@ -10,8 +34,10 @@ ESP8266WebServer server(80); const char* www_username = "admin"; const char* www_password = "esp8266"; -const char* www_realm = "Custom Realm"; // allows you to set the realm of authentication Default:"Login Required" -String AuthFailResponse = "Authentication Failed"; // the Content of the HTML response in case of Unautherized Access Default:empty + // allows you to set the realm of authentication Default:"Login Required" +const char* www_realm = "Custom Auth Realm"; +// the Content of the HTML response in case of Unautherized Access Default:empty +String authFailResponse = "Authentication Failed"; void setup() { Serial.begin(115200); @@ -26,10 +52,14 @@ void setup() { server.on("/", [](){ if(!server.authenticate(www_username, www_password)) - //return server.requestAuthentication(BASIC_AUTH, www_realm, AuthFailResponse); //Basic Auth Method with Custom realm and Failure Response - //return server.requestAuthentication(DIGEST_AUTH); //Digest Auth Method with realm="Login Required" and empty Failure Response - //return server.requestAuthentication(DIGEST_AUTH, www_realm); //Digest Auth Method with Custom realm and empty Failure Response - return server.requestAuthentication(DIGEST_AUTH, www_realm, AuthFailResponse); //Digest Auth Method with Custom realm and Failure Response + //Basic Auth Method with Custom realm and Failure Response + //return server.requestAuthentication(BASIC_AUTH, www_realm, authFailResponse); + //Digest Auth Method with realm="Login Required" and empty Failure Response + //return server.requestAuthentication(DIGEST_AUTH); + //Digest Auth Method with Custom realm and empty Failure Response + //return server.requestAuthentication(DIGEST_AUTH, www_realm); + //Digest Auth Method with Custom realm and Failure Response + return server.requestAuthentication(DIGEST_AUTH, www_realm, authFailResponse); server.send(200, "text/plain", "Login OK"); }); server.begin(); diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp b/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp index 594c6e8f3f..791f0bd14a 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp @@ -204,7 +204,16 @@ bool ESP8266WebServer::authenticate(const char * username, const char * password return false; } -void ESP8266WebServer::requestAuthentication(HTTPAuthMethod mode, const char* realm, const String& AuthFailMsg){ +String ESP8266WebServer::getRandomHexString(){ + char buffer[33]; // buffer to hold 32 Hex Digit + /0 + int i; + for(i=0;i<4;i++){ + sprintf (buffer+(i*8), "%08x", RANDOM_REG32); + } + return String(buffer); +} + +void ESP8266WebServer::requestAuthentication(HTTPAuthMethod mode, const char* realm, const String& authFailMsg){ if(realm==NULL){ _srealm = "Login Required"; }else{ @@ -213,11 +222,11 @@ void ESP8266WebServer::requestAuthentication(HTTPAuthMethod mode, const char* re if(mode==BASIC_AUTH){ sendHeader("WWW-Authenticate", "Basic realm=\"" + _srealm + "\""); }else{ - _snonce=String(RANDOM_REG32,HEX)+String(RANDOM_REG32,HEX)+String(RANDOM_REG32,HEX)+String(RANDOM_REG32,HEX); - _sopaque=String(RANDOM_REG32,HEX)+String(RANDOM_REG32,HEX)+String(RANDOM_REG32,HEX)+String(RANDOM_REG32,HEX); + _snonce=getRandomHexString(); + _sopaque=getRandomHexString(); sendHeader("WWW-Authenticate", "Digest realm=\"" +_srealm + "\", qop=\"auth\", nonce=\""+_snonce+"\", opaque=\""+_sopaque+"\""); } - send(401,"text/html",AuthFailMsg); + send(401,"text/html",authFailMsg); } void ESP8266WebServer::on(const String &uri, ESP8266WebServer::THandlerFunction handler) { diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer.h b/libraries/ESP8266WebServer/src/ESP8266WebServer.h index 36f8d4c22d..f24a5b690a 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer.h +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer.h @@ -79,7 +79,7 @@ class ESP8266WebServer void stop(); bool authenticate(const char * username, const char * password); - void requestAuthentication(HTTPAuthMethod mode = BASIC_AUTH, const char* realm = NULL, const String& AuthFailMsg = String("") ); + void requestAuthentication(HTTPAuthMethod mode = BASIC_AUTH, const char* realm = NULL, const String& authFailMsg = String("") ); typedef std::function THandlerFunction; void on(const String &uri, THandlerFunction handler); @@ -150,7 +150,8 @@ template size_t streamFile(T &file, const String& contentType){ uint8_t _uploadReadByte(WiFiClient& client); void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength); bool _collectHeader(const char* headerName, const char* headerValue); - + + String getRandomHexString(); // for extracting Auth parameters String _exractparam(String& authReq,const String& param,const char delimit = '"'); From 2ea36fb765fa5eaa8a1bace601761522c7001f5e Mon Sep 17 00:00:00 2001 From: Lan-Hekary Date: Tue, 12 Sep 2017 20:16:09 +0200 Subject: [PATCH 6/7] update license to public domain --- .../HttpAdvancedAuth/HttpAdvancedAuth.ino | 24 +++---------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/libraries/ESP8266WebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino b/libraries/ESP8266WebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino index 6f4a50bebb..72af50a4f1 100644 --- a/libraries/ESP8266WebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino +++ b/libraries/ESP8266WebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino @@ -1,25 +1,7 @@ /* - Http Advanced Authentication example - Copyright (c) 2017 Ahmed El-Sharnoby . All rights reserved. - This file is part of the ESP8266WebServer library for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - - Explaining the use of Advanced Authentication Methods to increase Security - Recommended to use the Digest Authentication Method as it's less vulnerable - than the Basic Method + HTTP Advanced Authentication example + Created Mar 16, 2017 by Ahmed El-Sharnoby. + This example code is in the public domain. */ #include From 35b7c0a35d8cb9c528d51c086139169bbde91545 Mon Sep 17 00:00:00 2001 From: Ahmed El-Sharnoby Date: Mon, 18 Sep 2017 11:29:34 +0200 Subject: [PATCH 7/7] renaming functions --- .../ESP8266WebServer/src/ESP8266WebServer.cpp | 24 +++++++++---------- .../ESP8266WebServer/src/ESP8266WebServer.h | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp b/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp index 791f0bd14a..8bc385328e 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer.cpp @@ -94,7 +94,7 @@ void ESP8266WebServer::begin() { collectHeaders(0, 0); } -String ESP8266WebServer::_exractparam(String& authReq,const String& param,const char delimit){ +String ESP8266WebServer::_exractParam(String& authReq,const String& param,const char delimit){ int _begin = authReq.indexOf(param); if (_begin==-1) return ""; return authReq.substring(_begin+param.length(),authReq.indexOf(delimit,_begin+param.length())); @@ -132,17 +132,17 @@ bool ESP8266WebServer::authenticate(const char * username, const char * password #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.println(authReq); #endif - String _username = _exractparam(authReq,"username=\""); + String _username = _exractParam(authReq,"username=\""); if((!_username.length())||_username!=String(username)){ authReq = String(); return false; } // extracting required parameters for RFC 2069 simpler Digest - String _realm = _exractparam(authReq,"realm=\""); - String _nonce = _exractparam(authReq,"nonce=\""); - String _uri = _exractparam(authReq,"uri=\""); - String _response = _exractparam(authReq,"response=\""); - String _opaque = _exractparam(authReq,"opaque=\""); + String _realm = _exractParam(authReq,"realm=\""); + String _nonce = _exractParam(authReq,"nonce=\""); + String _uri = _exractParam(authReq,"uri=\""); + String _response = _exractParam(authReq,"response=\""); + String _opaque = _exractParam(authReq,"opaque=\""); if((!_realm.length())||(!_nonce.length())||(!_uri.length())||(!_response.length())||(!_opaque.length())){ authReq = String(); @@ -155,8 +155,8 @@ bool ESP8266WebServer::authenticate(const char * username, const char * password // parameters for the RFC 2617 newer Digest String _nc,_cnonce; if(authReq.indexOf("qop=auth") != -1){ - _nc = _exractparam(authReq,"nc=",','); - _cnonce = _exractparam(authReq,"cnonce=\""); + _nc = _exractParam(authReq,"nc=",','); + _cnonce = _exractParam(authReq,"cnonce=\""); } MD5Builder md5; md5.begin(); @@ -204,7 +204,7 @@ bool ESP8266WebServer::authenticate(const char * username, const char * password return false; } -String ESP8266WebServer::getRandomHexString(){ +String ESP8266WebServer::_getRandomHexString(){ char buffer[33]; // buffer to hold 32 Hex Digit + /0 int i; for(i=0;i<4;i++){ @@ -222,8 +222,8 @@ void ESP8266WebServer::requestAuthentication(HTTPAuthMethod mode, const char* re if(mode==BASIC_AUTH){ sendHeader("WWW-Authenticate", "Basic realm=\"" + _srealm + "\""); }else{ - _snonce=getRandomHexString(); - _sopaque=getRandomHexString(); + _snonce=_getRandomHexString(); + _sopaque=_getRandomHexString(); sendHeader("WWW-Authenticate", "Digest realm=\"" +_srealm + "\", qop=\"auth\", nonce=\""+_snonce+"\", opaque=\""+_sopaque+"\""); } send(401,"text/html",authFailMsg); diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer.h b/libraries/ESP8266WebServer/src/ESP8266WebServer.h index f24a5b690a..cd410f5b3e 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer.h +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer.h @@ -151,9 +151,9 @@ template size_t streamFile(T &file, const String& contentType){ void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength); bool _collectHeader(const char* headerName, const char* headerValue); - String getRandomHexString(); + String _getRandomHexString(); // for extracting Auth parameters - String _exractparam(String& authReq,const String& param,const char delimit = '"'); + String _exractParam(String& authReq,const String& param,const char delimit = '"'); struct RequestArgument { String key;