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:
parent
3e9caf7a3d
commit
eebc5ec593
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,3 +10,4 @@ tools/sdk/lwip/src/build
|
||||
tools/sdk/lwip/src/liblwip_src.a
|
||||
|
||||
*.pyc
|
||||
*.gch
|
||||
|
@ -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();
|
||||
}
|
@ -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) {
|
||||
|
@ -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
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user