Kane12:
This code is in the Regulator.ino file. You make the digest request after serve the files. Why? I'm confused.
those are independent things. SusCalib talks to the web server of my Fronius photovoltaic system
Kane12:
This code is in the Regulator.ino file. You make the digest request after serve the files. Why? I'm confused.
those are independent things. SusCalib talks to the web server of my Fronius photovoltaic system
Your project is a labyrinth xD
ha1 = md5("service:W:PASSWORD") // replace PASSWORD
ha2 = md5("POST:/servicecgi-bin/suspend_battery_calibration/?method=save")
response = md5(ha1 + ":ffcb243c6f951a5bab771e9d3b8f81d6:" + ha2)
Which library will you use to compute hashes?
Kane12:
Your project is a labyrinth xDha1 = md5("service:W:PASSWORD") // replace PASSWORD
ha2 = md5("POST:/servicecgi-bin/suspend_battery_calibration/?method=save")
response = md5(ha1 + ":ffcb243c6f951a5bab771e9d3b8f81d6:" + ha2)
Which library will you use to compute hashes?
this is only a hint. my real secrets.h has constants calculated on a computer in Java
String nonce = "ffcb243c6f951a5bab771e9d3b8f81d6";
MessageDigest md5 = MessageDigest.getInstance("MD5");
String ha1 = byteArrayToHex(md5.digest("service:W:PASSWORD".getBytes()));
String ha2 = byteArrayToHex(md5.digest("POST:/servicecgi-bin/suspend_battery_calibration/?method=save".getBytes()));
String response = byteArrayToHex(md5.digest((ha1 + ":ffcb243c6f951a5bab771e9d3b8f81d6:" + ha2).getBytes()));
System.out.println(response);
public static String byteArrayToHex(byte[] a) {
StringBuilder sb = new StringBuilder(a.length * 2);
for(byte b: a) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
Ok.
For now I have implemented the Basic Authentication for practice. But, as you said, it's not enough strong. If Arduino could implement HTTPS it will be enough. I will try to implement Digest Authentication, but, as I have read, if a MITM gets the client response he can obtain the ID and PW because currently MD5 is vulnerable.
TY for your help!
Do you know if exists a library to eazy read the values of HTTP headers? Or I have to do it manually searching keywords in the body as 'nonce', 'response' and get its values?
Edit: is better do a manually search or save the entire HTTP client reponse in a buffer and play with string functions to get the values?
I would replace "else if" statements with "if" statements as HTTP does not require that one response be exclusive of another. Here is a snippit of code from another project.
if (str[0] == '/' && str[1] == ' '){// end of url, display just the web page
return(0);
}
if (strncmp("/t1.js",str,6)==0){
dat_p=print_t1js();
return(10);
}
if (strncmp("/config",str,7)==0){
dat_p=print_webpage_config();
return(10);
}
Is more efficient use strncmp or strstr?
Kane12:
Do you know if exists a library to eazy read the values of HTTP headers? Or I have to do it manually searching keywords in the body as 'nonce', 'response' and get its values?Edit: is better do a manually search or save the entire HTTP client reponse in a buffer and play with string functions to get the values?
I saw a HTTPClient library from Arduino, but it doesn't support F() macro so I ignored it.
I didn't need to parse more values from headers or body so I do not know the best way. I would start with parse as it arrives method.
I supose that isn't the best way to do it. But it works!
const char username[] = {'u', 's', 'e', 'r', 'n', 'a', 'm', 'e', '=', '"'};
bool usernameFound = false;
uint8_t usernamePosition = 0;
char usernameValue[20] = {0};
bool usernameObtained = false;
const char response[] = {'r', 'e', 's', 'p', 'o', 'n', 's', 'e', '=', '"'};
bool responseFound = false;
uint8_t responsePosition = 0;
char responseValue[33] = {0};
bool responseObtained = false;
if (username[usernamePosition] == c && !usernameFound) {
usernamePosition++;
} else if (!usernameFound){
usernamePosition = 0;
}
if (usernameFound && !usernameObtained) {
if (c != '"') {
usernameValue[usernamePosition] = c;
usernamePosition++;
} else {
usernameObtained = true;
}
}
if (usernamePosition == 10 && !usernameFound) {
usernameFound = true;
usernamePosition = 0;
}
if (response[responsePosition] == c && !responseFound) {
responsePosition++;
} else if (!responseFound){
responsePosition = 0;
}
if (responseFound && !responseObtained) {
if (c != '"') {
responseValue[responsePosition] = c;
responsePosition++;
} else {
responseObtained = true;
}
}
if (responsePosition == 10 && !responseFound) {
responseFound = true;
responsePosition = 0;
}
c is the character read of the HTTP response. I think that this is better than doing a 1K or 2K buffer to store the HTTP response only for obtain parts of its content.
EDIT: the full code is below.
Kane12:
c is the character read of the HTTP response. I think that this is better than doing a 1K or 2K buffer to store the HTTP response only for obtain parts of its content
I moved my server project to a 1284P mainly due to the buffer for outgoing data. The request was never even close to the size of data I wanted to send out. They can use the same buffer if one is trying to be real efficient. There are web servers that run on the 328P, but they don't give much data. unless you are implementing a complex file system, it's not necessary to handle traditional URLs, except where if affects the browser, such as when the browser needs a js, png, css etc. Most of the links navigating from page to page can be single letters.
I'm running the webserver on a ATmega2560 (8KB SRAM) and for now it's enough.
I have implemented the Digest Authentication but I have an important question:
My strategy to block users is that any user that tries to get files/information of the server has to have a cookie assigned. If a user doesn't have a cookie assigned I send a 401 Unauthorized response:
char a = '"';
bp.println(F("HTTP/1.1 401 Access Denied"));
bp.print(F("WWW-Authenticate: Digest realm=\"Arduino Web Server\", nonce="));
bp.print(a);
bp.print(nonceMd5Str);
bp.print(a);
bp.print(F(", opaque="));
bp.print(a);
bp.print(opaqueMd5Str);
bp.println(a);
bp.println(F("Connection: close"));
bp.println(F("Content-Length: 38"));
bp.println(F("Content-Type: text/plain; charset=utf-8"));
bp.println(F("Cache-Control: no-store"));
bp.println();
bp.println(F("Servidor protegit. Es requereix login!"));
bp.flush();
Where nonceMd5Str and opaqueMd5Str are random hashes that I create with millis() and millis() + 1 respectively.
When the client enters the User and PW I receive an HTTP request like this one:
GET / HTTP/1.1
***here is the hostname xD***
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Authorization: Digest username="Usuari", realm="Arduino Web Server", nonce="44d101b06c0867cd1311b6b99208172c", uri="/", response="92b88715f7bb629e429e5497731a354d", opaque="7ee8000fadece1fce405e57854a2d2f2"
response is calculated as follows:
A1 as md5("username:realm:password")
A2 as md5("requestMethod:requestURI")
response as md5("A1:nonce:A2")
If the server can match response hashes, it sets a cookie to the client:
bp.println(F("Set-Cookie: cookie=client; Max-Age=86400"));
See that I only have attached pseudocode. The full code is bigger and more complex. If anyone wants the code I can upload it.
And here is my question:
PD: is my cookies strategy reliable? Can I set the same cookie value to all the clients?
TY
Kane12:
I'm running the webserver on a ATmega2560 (8KB SRAM) and for now it's enough.I have implemented the Digest Authentication but I have an important question:
My strategy to block users is that any user that tries to get files/information of the server has to have a cookie assigned. If a user doesn't have a cookie assigned I send a 401 Unauthorized response:
char a = '"';
bp.println(F("HTTP/1.1 401 Access Denied"));
bp.print(F("WWW-Authenticate: Digest realm="Arduino Web Server", nonce="));
bp.print(a);
bp.print(nonceMd5Str);
bp.print(a);
bp.print(F(", opaque="));
bp.print(a);
bp.print(opaqueMd5Str);
bp.println(a);
bp.println(F("Connection: close"));
bp.println(F("Content-Length: 38"));
bp.println(F("Content-Type: text/plain; charset=utf-8"));
bp.println(F("Cache-Control: no-store"));
bp.println();
bp.println(F("Servidor protegit. Es requereix login!"));
bp.flush();
Where *nonceMd5Str* and *opaqueMd5Str* are random hashes that I create with *millis()* and *millis() + 1* respectively. When the client enters the User and PW I receive an HTTP request like this one:
GET / HTTP/1.1
here is the hostname xD
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
Accept-Language: es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Authorization: Digest username="Usuari", realm="Arduino Web Server", nonce="44d101b06c0867cd1311b6b99208172c", uri="/", response="92b88715f7bb629e429e5497731a354d", opaque="7ee8000fadece1fce405e57854a2d2f2"*response* is calculated as follows:
A1 as md5("username:realm:password")
A2 as md5("requestMethod:requestURI")
response as md5("A1:nonce:A2")And here is my question: - It's enough to calculate the response hash of the server part and match it with the hash received of the client part? Or I have to check more things? PD: is my cookies strategy reliable? TY
I am no expert on server security, but without safeguarding the transport layer, I wouldn't share personal information.
I am no expert on server security, but without safeguarding the transport layer, I wouldn't share personal information.
As I know, HTTPS can't be implemented with Arduino...
In my case, I don't serve sensitive information. I only serve temperature data. The unique dangerous thing is that a client can change some configurations of the temperature probes and can delete the temperature data register. Because of this, I need some security.
Edit: I'm thinking about that a second security layer could be that the client has to resolve an easy numeric algorithm that only the server knows. The server sends, for example, 3 numbers and the client has to resolve the algorithm. These 3 numbers could change every time a client tries to access the server. This could resolve the MITM problem and make the server security more robust.
@Perehama, please read the thread from the beginning
Here's the full code for Digest Authentication.
void respondClientsRequest() {
bool currentLineIsBlank = true;
char pathHTTPRequest[HTTP_BUF_SZ] = {0};
uint8_t pathHTTPRIndex = 0;
uint8_t numberOfSpaces = 0;
char c;
const char cookie[] = "Cookie: cookie=";
bool cookieFound = false;
uint8_t cookiePosition = 0;
char cookieValue[15] = {0};
bool cookieObtained = false;
const char authorization[] = "Authorization: Digest ";
bool authorizationFound = false;
uint8_t authorizationPosition = 0;
const char opaque[] = {'o', 'p', 'a', 'q', 'u', 'e', '=', '"'};
bool opaqueFound = false;
uint8_t opaquePosition = 0;
char opaqueValue[33] = {0};
bool opaqueObtained = false;
const char response[] = {'r', 'e', 's', 'p', 'o', 'n', 's', 'e', '=', '"'};
bool responseFound = false;
uint8_t responsePosition = 0;
char responseValue[33] = {0};
bool responseObtained = false;
const char usernameServer[] = "Usuari";
const char passwordServer[] = "Password";
const char realm[] = "Arduino Web Server";
const char requestMethod[] = "GET";
const char requestURI[] = "/";
while (client.connected()) {
if (client.available()) {
c = client.read();
if (cookie[cookiePosition] == c && !cookieFound) {
cookiePosition++;
} else if (!cookieFound) {
cookiePosition = 0;
}
if (cookieFound && !cookieObtained) {
if (cookiePosition <= 13) {
cookieValue[cookiePosition] = c;
cookiePosition++;
} else {
cookieObtained = true;
}
}
if (cookiePosition == 15 && !cookieFound) {
cookieFound = true;
cookiePosition = 0;
}
if (authorization[authorizationPosition] == c && !authorizationFound) {
authorizationPosition++;
} else if (!authorizationFound){
authorizationPosition = 0;
}
if (authorizationPosition == 22 && !authorizationFound) {
authorizationFound = true;
}
if (authorizationFound) {
if (opaque[opaquePosition] == c && !opaqueFound) {
opaquePosition++;
} else if (!opaqueFound){
opaquePosition = 0;
}
if (opaqueFound && !opaqueObtained) {
if (opaquePosition <= 31) {
opaqueValue[opaquePosition] = c;
opaquePosition++;
} else {
opaqueObtained = true;
}
}
if (opaquePosition == 8 && !opaqueFound) {
opaqueFound = true;
opaquePosition = 0;
}
if (response[responsePosition] == c && !responseFound) {
responsePosition++;
} else if (!responseFound){
responsePosition = 0;
}
if (responseFound && !responseObtained) {
if (responsePosition <= 31) {
responseValue[responsePosition] = c;
responsePosition++;
} else {
responseObtained = true;
}
}
if (responsePosition == 10 && !responseFound) {
responseFound = true;
responsePosition = 0;
}
}
if (c == ' ' && numberOfSpaces < 2) {
numberOfSpaces++;
}
if (numberOfSpaces == 1 && pathHTTPRIndex < (HTTP_BUF_SZ - 1) && c != ' ') {
pathHTTPRequest[pathHTTPRIndex] = c;
pathHTTPRIndex++;
}
if (c == '\n' && currentLineIsBlank) {
if (cookieFound) {
if (pathHTTPRIndex == 1) {
strcpy_P(pathHTTPRequest, (const char*) F("/webpage/html/home.htm"));
serveFile(pathHTTPRequest);
} else if (strchr(pathHTTPRequest, '.')) {
serveFile(pathHTTPRequest);
} else if (strstr(pathHTTPRequest, "XML")){
serveXMLData(pathHTTPRequest);
} else {
serveConfigInfo(pathHTTPRequest);
}
break;
} else {
static char scNonce[33] = {0};
static char scOpaque[33] = {0};
bool authorizedAccess = false;
if (authorizationFound) {
char userRealmPass[40] = {0};
char methodURI[10] = {0};
char a1NonceA2[97] = {0};
sprintf(userRealmPass, "%s:%s:%s", usernameServer, realm, passwordServer);
sprintf(methodURI, "%s:%s", requestMethod, requestURI);
unsigned char* a1Hash = MD5::make_hash(userRealmPass);
char* a1Md5Str = MD5::make_digest(a1Hash, 16);
unsigned char* a2Hash = MD5::make_hash(methodURI);
char* a2Md5Str = MD5::make_digest(a2Hash, 16);
sprintf(a1NonceA2, "%s:%s:%s", a1Md5Str, scNonce, a2Md5Str);
unsigned char* responseHash = MD5::make_hash(a1NonceA2);
char* responseMd5Str = MD5::make_digest(responseHash, 16);
if (strcmp(responseValue, responseMd5Str) == 0 && strcmp(opaqueValue, scOpaque) == 0) {
authorizedAccess = true;
}
}
if (!authorizedAccess) {
unsigned char* nonceHash = MD5::make_hash(millis());
char* nonceMd5Str = MD5::make_digest(nonceHash, 16);
sprintf(scNonce, "%s", nonceMd5Str);
unsigned char* opaqueHash = MD5::make_hash(millis() + 1);
char* opaqueMd5Str = MD5::make_digest(opaqueHash, 16);
sprintf(scOpaque, "%s", opaqueMd5Str);
HTTPAnswerAccessDenied(scNonce, scOpaque);
} else {
strcpy_P(pathHTTPRequest, (const char*) F("/webpage/html/home.htm"));
serveFile(pathHTTPRequest);
}
break;
}
}
if (c == '\n') {
currentLineIsBlank = true;
} else if (c != '\r') {
currentLineIsBlank = false;
}
}
}
client.stop();
}
EDIT: I'm implementing an improved version for DA. I will upload it soon!