Skip to content

Commit eebc5ec

Browse files
Lan-Hekaryigrr
authored andcommitted
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
1 parent 3e9caf7 commit eebc5ec

File tree

4 files changed

+168
-4
lines changed

4 files changed

+168
-4
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ tools/sdk/lwip/src/build
1010
tools/sdk/lwip/src/liblwip_src.a
1111

1212
*.pyc
13+
*.gch
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
HTTP Advanced Authentication example
3+
Created Mar 16, 2017 by Ahmed El-Sharnoby.
4+
This example code is in the public domain.
5+
*/
6+
7+
#include <ESP8266WiFi.h>
8+
#include <ESP8266mDNS.h>
9+
#include <ArduinoOTA.h>
10+
#include <ESP8266WebServer.h>
11+
12+
const char* ssid = "........";
13+
const char* password = "........";
14+
15+
ESP8266WebServer server(80);
16+
17+
const char* www_username = "admin";
18+
const char* www_password = "esp8266";
19+
// allows you to set the realm of authentication Default:"Login Required"
20+
const char* www_realm = "Custom Auth Realm";
21+
// the Content of the HTML response in case of Unautherized Access Default:empty
22+
String authFailResponse = "Authentication Failed";
23+
24+
void setup() {
25+
Serial.begin(115200);
26+
WiFi.mode(WIFI_STA);
27+
WiFi.begin(ssid, password);
28+
if(WiFi.waitForConnectResult() != WL_CONNECTED) {
29+
Serial.println("WiFi Connect Failed! Rebooting...");
30+
delay(1000);
31+
ESP.restart();
32+
}
33+
ArduinoOTA.begin();
34+
35+
server.on("/", [](){
36+
if(!server.authenticate(www_username, www_password))
37+
//Basic Auth Method with Custom realm and Failure Response
38+
//return server.requestAuthentication(BASIC_AUTH, www_realm, authFailResponse);
39+
//Digest Auth Method with realm="Login Required" and empty Failure Response
40+
//return server.requestAuthentication(DIGEST_AUTH);
41+
//Digest Auth Method with Custom realm and empty Failure Response
42+
//return server.requestAuthentication(DIGEST_AUTH, www_realm);
43+
//Digest Auth Method with Custom realm and Failure Response
44+
return server.requestAuthentication(DIGEST_AUTH, www_realm, authFailResponse);
45+
server.send(200, "text/plain", "Login OK");
46+
});
47+
server.begin();
48+
49+
Serial.print("Open http://");
50+
Serial.print(WiFi.localIP());
51+
Serial.println("/ in your browser to see it working");
52+
}
53+
54+
void loop() {
55+
ArduinoOTA.handle();
56+
server.handleClient();
57+
}

libraries/ESP8266WebServer/src/ESP8266WebServer.cpp

+100-3
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ void ESP8266WebServer::begin() {
9494
collectHeaders(0, 0);
9595
}
9696

97+
String ESP8266WebServer::_exractParam(String& authReq,const String& param,const char delimit){
98+
int _begin = authReq.indexOf(param);
99+
if (_begin==-1) return "";
100+
return authReq.substring(_begin+param.length(),authReq.indexOf(delimit,_begin+param.length()));
101+
}
102+
97103
bool ESP8266WebServer::authenticate(const char * username, const char * password){
98104
if(hasHeader(AUTHORIZATION_HEADER)){
99105
String authReq = header(AUTHORIZATION_HEADER);
@@ -121,15 +127,106 @@ bool ESP8266WebServer::authenticate(const char * username, const char * password
121127
}
122128
delete[] toencode;
123129
delete[] encoded;
130+
}else if(authReq.startsWith("Digest")){
131+
authReq = authReq.substring(7);
132+
#ifdef DEBUG_ESP_HTTP_SERVER
133+
DEBUG_OUTPUT.println(authReq);
134+
#endif
135+
String _username = _exractParam(authReq,"username=\"");
136+
if((!_username.length())||_username!=String(username)){
137+
authReq = String();
138+
return false;
139+
}
140+
// extracting required parameters for RFC 2069 simpler Digest
141+
String _realm = _exractParam(authReq,"realm=\"");
142+
String _nonce = _exractParam(authReq,"nonce=\"");
143+
String _uri = _exractParam(authReq,"uri=\"");
144+
String _response = _exractParam(authReq,"response=\"");
145+
String _opaque = _exractParam(authReq,"opaque=\"");
146+
147+
if((!_realm.length())||(!_nonce.length())||(!_uri.length())||(!_response.length())||(!_opaque.length())){
148+
authReq = String();
149+
return false;
150+
}
151+
if((_opaque!=_sopaque)||(_nonce!=_snonce)||(_realm!=_srealm)){
152+
authReq = String();
153+
return false;
154+
}
155+
// parameters for the RFC 2617 newer Digest
156+
String _nc,_cnonce;
157+
if(authReq.indexOf("qop=auth") != -1){
158+
_nc = _exractParam(authReq,"nc=",',');
159+
_cnonce = _exractParam(authReq,"cnonce=\"");
160+
}
161+
MD5Builder md5;
162+
md5.begin();
163+
md5.add(String(username)+":"+_realm+":"+String(password)); // md5 of the user:realm:user
164+
md5.calculate();
165+
String _H1 = md5.toString();
166+
#ifdef DEBUG_ESP_HTTP_SERVER
167+
DEBUG_OUTPUT.println("Hash of user:realm:pass=" + _H1);
168+
#endif
169+
md5.begin();
170+
if(_currentMethod == HTTP_GET){
171+
md5.add("GET:"+_uri);
172+
}else if(_currentMethod == HTTP_POST){
173+
md5.add("POST:"+_uri);
174+
}else if(_currentMethod == HTTP_PUT){
175+
md5.add("PUT:"+_uri);
176+
}else if(_currentMethod == HTTP_DELETE){
177+
md5.add("DELETE:"+_uri);
178+
}else{
179+
md5.add("GET:"+_uri);
180+
}
181+
md5.calculate();
182+
String _H2 = md5.toString();
183+
#ifdef DEBUG_ESP_HTTP_SERVER
184+
DEBUG_OUTPUT.println("Hash of GET:uri=" + _H2);
185+
#endif
186+
md5.begin();
187+
if(authReq.indexOf("qop=auth") != -1){
188+
md5.add(_H1+":"+_nonce+":"+_nc+":"+_cnonce+":auth:"+_H2);
189+
}else{
190+
md5.add(_H1+":"+_nonce+":"+_H2);
191+
}
192+
md5.calculate();
193+
String _responsecheck = md5.toString();
194+
#ifdef DEBUG_ESP_HTTP_SERVER
195+
DEBUG_OUTPUT.println("The Proper response=" +_responsecheck);
196+
#endif
197+
if(_response==_responsecheck){
198+
authReq = String();
199+
return true;
200+
}
124201
}
125202
authReq = String();
126203
}
127204
return false;
128205
}
129206

130-
void ESP8266WebServer::requestAuthentication(){
131-
sendHeader("WWW-Authenticate", "Basic realm=\"Login Required\"");
132-
send(401);
207+
String ESP8266WebServer::_getRandomHexString(){
208+
char buffer[33]; // buffer to hold 32 Hex Digit + /0
209+
int i;
210+
for(i=0;i<4;i++){
211+
sprintf (buffer+(i*8), "%08x", RANDOM_REG32);
212+
}
213+
return String(buffer);
214+
}
215+
216+
void ESP8266WebServer::requestAuthentication(HTTPAuthMethod mode, const char* realm, const String& authFailMsg){
217+
if(realm==NULL){
218+
_srealm = "Login Required";
219+
}else{
220+
_srealm = String(realm);
221+
}
222+
if(mode==BASIC_AUTH){
223+
sendHeader("WWW-Authenticate", "Basic realm=\"" + _srealm + "\"");
224+
}else{
225+
_snonce=_getRandomHexString();
226+
_sopaque=_getRandomHexString();
227+
sendHeader("WWW-Authenticate", "Digest realm=\"" +_srealm + "\", qop=\"auth\", nonce=\""+_snonce+"\", opaque=\""+_sopaque+"\"");
228+
}
229+
send(401,"text/html",authFailMsg);
133230
}
134231

135232
void ESP8266WebServer::on(const String &uri, ESP8266WebServer::THandlerFunction handler) {

libraries/ESP8266WebServer/src/ESP8266WebServer.h

+10-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ enum HTTPMethod { HTTP_ANY, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELE
3131
enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END,
3232
UPLOAD_FILE_ABORTED };
3333
enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE };
34+
enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH };
3435

3536
#define HTTP_DOWNLOAD_UNIT_SIZE 1460
3637

@@ -78,7 +79,7 @@ class ESP8266WebServer
7879
void stop();
7980

8081
bool authenticate(const char * username, const char * password);
81-
void requestAuthentication();
82+
void requestAuthentication(HTTPAuthMethod mode = BASIC_AUTH, const char* realm = NULL, const String& authFailMsg = String("") );
8283

8384
typedef std::function<void(void)> THandlerFunction;
8485
void on(const String &uri, THandlerFunction handler);
@@ -149,6 +150,10 @@ template<typename T> size_t streamFile(T &file, const String& contentType){
149150
uint8_t _uploadReadByte(WiFiClient& client);
150151
void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength);
151152
bool _collectHeader(const char* headerName, const char* headerValue);
153+
154+
String _getRandomHexString();
155+
// for extracting Auth parameters
156+
String _exractParam(String& authReq,const String& param,const char delimit = '"');
152157

153158
struct RequestArgument {
154159
String key;
@@ -182,6 +187,10 @@ template<typename T> size_t streamFile(T &file, const String& contentType){
182187
String _hostHeader;
183188
bool _chunked;
184189

190+
String _snonce; // Store noance and opaque for future comparison
191+
String _sopaque;
192+
String _srealm; // Store the Auth realm between Calls
193+
185194
};
186195

187196

0 commit comments

Comments
 (0)