1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-04-21 10:26:06 +03:00

Digest Authentication in Webserver Library (#3053)

* Add Digest Auth

* Check for Opaque and Nonce

* Remove Serial Debug and fix Indentation

* Added example sketch with documentation,Fixed indentation and Defaults

* Digest Authentication minor changes + new padded 32 digit random function

* update license to public domain

* renaming functions
This commit is contained in:
Ahmed El Sharnoby 2017-09-18 13:31:32 +03:00 committed by Ivan Grokhotkov
parent 3e9caf7a3d
commit eebc5ec593
4 changed files with 168 additions and 4 deletions

1
.gitignore vendored
View File

@ -10,3 +10,4 @@ tools/sdk/lwip/src/build
tools/sdk/lwip/src/liblwip_src.a
*.pyc
*.gch

View File

@ -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 <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <ArduinoOTA.h>
#include <ESP8266WebServer.h>
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();
}

View File

@ -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) {

View File

@ -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<void(void)> THandlerFunction;
void on(const String &uri, THandlerFunction handler);
@ -150,6 +151,10 @@ protected:
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;
String value;
@ -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
};