Skip to content

Commit 49b6046

Browse files
author
Me No Dev
committed
Add Digest Web Authentication and make it default
accept htdigest formatted hashes as well
1 parent cdd1ced commit 49b6046

6 files changed

+312
-34
lines changed

.travis.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ script:
3535
- arduino --board esp8266com:esp8266:generic --save-prefs
3636
- arduino --get-pref sketchbook.path
3737
- build_sketches arduino $HOME/Arduino/libraries/ESPAsyncWebServer esp8266
38-
- arduino --board espressif:ESP31B:esp31b --save-prefs
39-
- arduino --get-pref sketchbook.path
40-
- build_sketches arduino $HOME/Arduino/libraries/ESPAsyncWebServer esp31b
38+
# - arduino --board espressif:ESP31B:esp31b --save-prefs
39+
# - arduino --get-pref sketchbook.path
40+
# - build_sketches arduino $HOME/Arduino/libraries/ESPAsyncWebServer esp31b
4141

4242
notifications:
4343
email:

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ To use this library you need to have the latest git versions of either [ESP8266]
1414
- When you send the response, you are immediately ready to handle other connections
1515
while the server is taking care of sending the response in the background
1616
- Speed is OMG
17-
- Easy to use API, HTTP Basic Authentication, ChunkedResponse
17+
- Easy to use API, HTTP Basic and Digest MD5 Authentication (default), ChunkedResponse
1818
- Easily extendible to handle any type of content
1919
- Supports Continue 100
2020
- Async WebSocket plugin offering different locations without extra servers or ports
21-
- Async EventSource (ServerSideEvents) plugin to send events to the browser
21+
- Async EventSource (Server-Sent Events) plugin to send events to the browser
2222
- URL Rewrite plugin for conditional and permanent url rewrites
2323
- ServeStatic plugin that supports cache, Last-Modified, default index and more
2424

@@ -668,7 +668,7 @@ client->binary(flash_binary, 4);
668668
```
669669

670670
## Async Event Source Plugin
671-
The server includes EventSource (ServerSideEvents) plugin which can be used to send short text events to the browser.
671+
The server includes EventSource (Server-Sent Events) plugin which can be used to send short text events to the browser.
672672
Difference between EventSource and WebSockets is that EventSource is single direction, text-only protocol.
673673

674674

@@ -679,7 +679,7 @@ Difference between EventSource and WebSockets is that EventSource is single dire
679679

680680
AsyncWebServer server(80);
681681
AsyncWebSocket ws("/ws"); // access at ws://[esp ip]/ws
682-
AsyncEventSource events("/events"); // event source (server side events)
682+
AsyncEventSource events("/events"); // event source (Server-Sent events)
683683

684684
const char* ssid = "your-ssid";
685685
const char* password = "your-pass";

src/ESPAsyncWebServer.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ class AsyncWebServerRequest {
129129
String _contentType;
130130
String _boundary;
131131
String _authorization;
132+
bool _isDigest;
132133
bool _isMultipart;
133134
bool _isPlainPost;
134135
bool _expectingContinue;
@@ -188,9 +189,13 @@ class AsyncWebServerRequest {
188189
bool multipart(){ return _isMultipart; }
189190
const char * methodToString();
190191

191-
bool authenticate(const char * username, const char * password);
192+
193+
//hash is the string representation of:
194+
// base64(user:pass) for basic or
195+
// user:realm:md5(user:realm:pass) for digest
192196
bool authenticate(const char * hash);
193-
void requestAuthentication();
197+
bool authenticate(const char * username, const char * password, const char * realm = NULL, bool passwordIsHash = false);
198+
void requestAuthentication(const char * realm = NULL, bool isDigest = true);
194199

195200
void setHandler(AsyncWebHandler *handler){ _handler = handler; }
196201
void addInterestingHeader(String name);

src/WebAuthentication.cpp

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
/*
2+
Asynchronous WebServer library for Espressif MCUs
3+
4+
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
5+
This file is part of the esp8266 core for Arduino environment.
6+
7+
This library is free software; you can redistribute it and/or
8+
modify it under the terms of the GNU Lesser General Public
9+
License as published by the Free Software Foundation; either
10+
version 2.1 of the License, or (at your option) any later version.
11+
12+
This library is distributed in the hope that it will be useful,
13+
but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
Lesser General Public License for more details.
16+
17+
You should have received a copy of the GNU Lesser General Public
18+
License along with this library; if not, write to the Free Software
19+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20+
*/
21+
#include "WebAuthentication.h"
22+
#include <libb64/cencode.h>
23+
#include "md5.h"
24+
25+
// Basic Auth hash = base64("username:password")
26+
27+
bool checkBasicAuthentication(const char * hash, const char * username, const char * password){
28+
if(username == NULL || password == NULL || hash == NULL)
29+
return false;
30+
31+
size_t toencodeLen = os_strlen(username)+os_strlen(password)+1;
32+
size_t encodedLen = base64_encode_expected_len(toencodeLen);
33+
if(strlen(hash) != encodedLen)
34+
return false;
35+
36+
char *toencode = new char[toencodeLen+1];
37+
if(toencode == NULL){
38+
return false;
39+
}
40+
char *encoded = new char[base64_encode_expected_len(toencodeLen)+1];
41+
if(encoded == NULL){
42+
delete[] toencode;
43+
return false;
44+
}
45+
sprintf(toencode, "%s:%s", username, password);
46+
if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && memcmp(hash, encoded, encodedLen) == 0){
47+
delete[] toencode;
48+
delete[] encoded;
49+
return true;
50+
}
51+
delete[] toencode;
52+
delete[] encoded;
53+
return false;
54+
}
55+
56+
static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or more
57+
md5_context_t _ctx;
58+
uint8_t i;
59+
uint8_t * _buf = (uint8_t*)malloc(16);
60+
if(_buf == NULL)
61+
return false;
62+
memset(_buf, 0x00, 16);
63+
MD5Init(&_ctx);
64+
MD5Update(&_ctx, data, len);
65+
MD5Final(_buf, &_ctx);
66+
for(i = 0; i < 16; i++) {
67+
sprintf(output + (i * 2), "%02x", _buf[i]);
68+
}
69+
free(_buf);
70+
return true;
71+
}
72+
73+
static String genRandomMD5(){
74+
uint32_t r = RANDOM_REG32;
75+
char * out = (char*)malloc(33);
76+
if(out == NULL || !getMD5((uint8_t*)(&r), 4, out))
77+
return "";
78+
String res = String(out);
79+
free(out);
80+
return res;
81+
}
82+
83+
static String stringMD5(String in){
84+
char * out = (char*)malloc(33);
85+
if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
86+
return "";
87+
String res = String(out);
88+
free(out);
89+
return res;
90+
}
91+
92+
String generateDigestHash(const char * username, const char * password, const char * realm){
93+
if(username == NULL || password == NULL || realm == NULL){
94+
return "";
95+
}
96+
char * out = (char*)malloc(33);
97+
String res = String(username);
98+
res.concat(":");
99+
res.concat(realm);
100+
res.concat(":");
101+
String in = res;
102+
in.concat(password);
103+
if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
104+
return "";
105+
res.concat(out);
106+
free(out);
107+
return res;
108+
}
109+
110+
String requestDigestAuthentication(const char * realm){
111+
String header = "realm=\"";
112+
if(realm == NULL)
113+
header.concat("asyncesp");
114+
else
115+
header.concat(realm);
116+
header.concat( "\", qop=\"auth\", nonce=\"");
117+
header.concat(genRandomMD5());
118+
header.concat("\", opaque=\"");
119+
header.concat(genRandomMD5());
120+
header.concat("\"");
121+
return header;
122+
}
123+
124+
bool checkDigestAuthentication(const char * header, const char * method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri){
125+
if(username == NULL || password == NULL || header == NULL || method == NULL){
126+
//os_printf("AUTH FAIL: missing requred fields\n");
127+
return false;
128+
}
129+
130+
String myHeader = String(header);
131+
int nextBreak = myHeader.indexOf(", ");
132+
if(nextBreak < 0){
133+
//os_printf("AUTH FAIL: no variables\n");
134+
return false;
135+
}
136+
137+
String myUsername = String();
138+
String myRealm = String();
139+
String myNonce = String();
140+
String myUri = String();
141+
String myResponse = String();
142+
String myQop = String();
143+
String myNc = String();
144+
String myCnonce = String();
145+
146+
myHeader += ", ";
147+
do {
148+
String avLine = myHeader.substring(0, nextBreak);
149+
myHeader = myHeader.substring(nextBreak+2);
150+
nextBreak = myHeader.indexOf(", ");
151+
152+
int eqSign = avLine.indexOf("=");
153+
if(eqSign < 0){
154+
//os_printf("AUTH FAIL: no = sign\n");
155+
return false;
156+
}
157+
String varName = avLine.substring(0, eqSign);
158+
avLine = avLine.substring(eqSign + 1);
159+
if(avLine.startsWith("\"")){
160+
avLine = avLine.substring(1, avLine.length() - 1);
161+
}
162+
163+
if(varName.equals("username")){
164+
if(!avLine.equals(username)){
165+
//os_printf("AUTH FAIL: username\n");
166+
return false;
167+
}
168+
myUsername = avLine;
169+
} else if(varName.equals("realm")){
170+
if(realm != NULL && !avLine.equals(realm)){
171+
//os_printf("AUTH FAIL: realm\n");
172+
return false;
173+
}
174+
myRealm = avLine;
175+
} else if(varName.equals("nonce")){
176+
if(nonce != NULL && !avLine.equals(nonce)){
177+
//os_printf("AUTH FAIL: nonce\n");
178+
return false;
179+
}
180+
myNonce = avLine;
181+
} else if(varName.equals("opaque")){
182+
if(opaque != NULL && !avLine.equals(opaque)){
183+
//os_printf("AUTH FAIL: opaque\n");
184+
return false;
185+
}
186+
} else if(varName.equals("uri")){
187+
if(uri != NULL && !avLine.equals(uri)){
188+
//os_printf("AUTH FAIL: uri\n");
189+
return false;
190+
}
191+
myUri = avLine;
192+
} else if(varName.equals("response")){
193+
myResponse = avLine;
194+
} else if(varName.equals("qop")){
195+
myQop = avLine;
196+
} else if(varName.equals("nc")){
197+
myNc = avLine;
198+
} else if(varName.equals("cnonce")){
199+
myCnonce = avLine;
200+
}
201+
} while(nextBreak > 0);
202+
203+
String ha1 = (passwordIsHash) ? String(password) : myUsername + ":" + myRealm + ":" + String(password);
204+
String ha2 = String(method) + ":" + myUri;
205+
String response = stringMD5(ha1) + ":" + myNonce + ":" + myNc + ":" + myCnonce + ":" + myQop + ":" + stringMD5(ha2);
206+
207+
if(myResponse.equals(stringMD5(response))){
208+
//os_printf("AUTH SUCCESS\n");
209+
return true;
210+
}
211+
212+
//os_printf("AUTH FAIL: password\n");
213+
return false;
214+
}

src/WebAuthentication.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
Asynchronous WebServer library for Espressif MCUs
3+
4+
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
5+
This file is part of the esp8266 core for Arduino environment.
6+
7+
This library is free software; you can redistribute it and/or
8+
modify it under the terms of the GNU Lesser General Public
9+
License as published by the Free Software Foundation; either
10+
version 2.1 of the License, or (at your option) any later version.
11+
12+
This library is distributed in the hope that it will be useful,
13+
but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
Lesser General Public License for more details.
16+
17+
You should have received a copy of the GNU Lesser General Public
18+
License along with this library; if not, write to the Free Software
19+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20+
*/
21+
22+
#ifndef WEB_AUTHENTICATION_H_
23+
#define WEB_AUTHENTICATION_H_
24+
25+
#include "Arduino.h"
26+
27+
bool checkBasicAuthentication(const char * header, const char * username, const char * password);
28+
String requestDigestAuthentication(const char * realm);
29+
bool checkDigestAuthentication(const char * header, const char * method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri);
30+
31+
//for storing hashed versions on the device that can be authenticated against
32+
String generateDigestHash(const char * username, const char * password, const char * realm);
33+
34+
#endif

0 commit comments

Comments
 (0)