Skip to content

Digest Authentication in Webserver Library #3053

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 20 commits into from
Sep 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ tools/sdk/lwip/src/build
tools/sdk/lwip/src/liblwip_src.a

*.pyc
*.gch
Original file line number Diff line number Diff line change
@@ -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>
Copy link
Member

Choose a reason for hiding this comment

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

Please add a public domain / CC0 license at the top of the sketch

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I copied this sketch from the HttpBasicAuth folder and it does not have a license ..
I am not sure of what should I put here ..
can you give an Example ??

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, i mean public domain license, as in Blink example. It has been mentioned before that using LGPL license on examples is too restrictive. We already have a few such examples, and it would be better to migrate all the examples to CC0/public domain license.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sorry I just copied the license from the next sketch in the folder .
fixed it now

#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();
}
103 changes: 100 additions & 3 deletions libraries/ESP8266WebServer/src/ESP8266WebServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down
11 changes: 10 additions & 1 deletion libraries/ESP8266WebServer/src/ESP8266WebServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -78,7 +79,7 @@ class ESP8266WebServer
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);
Expand Down Expand Up @@ -149,6 +150,10 @@ template<typename T> 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 = '"');

struct RequestArgument {
String key;
Expand Down Expand Up @@ -182,6 +187,10 @@ template<typename T> size_t streamFile(T &file, const String& contentType){
String _hostHeader;
bool _chunked;

String _snonce; // Store noance and opaque for future comparison
String _sopaque;
String _srealm; // Store the Auth realm between Calls

};


Expand Down