diff --git a/.gitignore b/.gitignore index c36bdd3aa..7d74b8bcf 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 000000000..72af50a4f --- /dev/null +++ b/libraries/ESP8266WebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino @@ -0,0 +1,57 @@ +/* + HTTP Advanced Authentication example + Created Mar 16, 2017 by Ahmed El-Sharnoby. + This example code is in the public domain. +*/ + +#include +#include +#include +#include + +const char* ssid = "........"; +const char* password = "........"; + +ESP8266WebServer server(80); + +const char* www_username = "admin"; +const char* www_password = "esp8266"; + // 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); + 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)) + //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(); + + 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 bc41a5603..8bc385328 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,106 @@ 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; + } + 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=\""); + } + 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; + } } authReq = String(); } return false; } -void ESP8266WebServer::requestAuthentication(){ - sendHeader("WWW-Authenticate", "Basic realm=\"Login Required\""); - send(401); +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{ + _srealm = String(realm); + } + if(mode==BASIC_AUTH){ + sendHeader("WWW-Authenticate", "Basic realm=\"" + _srealm + "\""); + }else{ + _snonce=_getRandomHexString(); + _sopaque=_getRandomHexString(); + 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 ab22fe659..cd410f5b3 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 @@ -78,7 +79,7 @@ public: void stop(); bool authenticate(const char * username, const char * password); - void requestAuthentication(); + 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); @@ -149,6 +150,10 @@ protected: 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 = '"'); struct RequestArgument { String key; @@ -182,6 +187,10 @@ protected: String _hostHeader; bool _chunked; + String _snonce; // Store noance and opaque for future comparison + String _sopaque; + String _srealm; // Store the Auth realm between Calls + };