From 15141be83517fef12f6c9e31e7cccb75cb9bcb31 Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Sat, 29 Oct 2022 12:35:20 +0200 Subject: [PATCH 01/21] First stab ad simplyfing webserver auth and adding a handler. --- .../HttpAuthCallback/HttpAuthCallback.ino | 63 +++++ .../HttpAuthCallbackInline.ino | 62 +++++ .../HttpAuthOneTimePassword.ino | 215 ++++++++++++++++ .../HttpAuthOneTimePasswordNaive.ino | 125 ++++++++++ .../HttpBasicAuthSHA1/HttpBasicAuthSHA1.ino | 75 ++++++ libraries/WebServer/src/WebServer.cpp | 235 +++++++++++------- libraries/WebServer/src/WebServer.h | 9 + 7 files changed, 699 insertions(+), 85 deletions(-) create mode 100644 libraries/WebServer/examples/HttpAuthCallback/HttpAuthCallback.ino create mode 100644 libraries/WebServer/examples/HttpAuthCallbackInline/HttpAuthCallbackInline.ino create mode 100644 libraries/WebServer/examples/HttpAuthOneTimePassword/HttpAuthOneTimePassword.ino create mode 100644 libraries/WebServer/examples/HttpAuthOneTimePasswordNaive/HttpAuthOneTimePasswordNaive.ino create mode 100644 libraries/WebServer/examples/HttpBasicAuthSHA1/HttpBasicAuthSHA1.ino diff --git a/libraries/WebServer/examples/HttpAuthCallback/HttpAuthCallback.ino b/libraries/WebServer/examples/HttpAuthCallback/HttpAuthCallback.ino new file mode 100644 index 00000000000..1fc99d826ae --- /dev/null +++ b/libraries/WebServer/examples/HttpAuthCallback/HttpAuthCallback.ino @@ -0,0 +1,63 @@ +#include +#include +#include +#include + +const char* ssid = "........"; +const char* password = "........"; + +WebServer server(80); + +typedef struct credentials_t { + String username; + String password; +} credentials_t; + +credentials_t passwdfile[] = { + { "admin", "esp32" }, + { "fred", "41234123" }, + { "charlie", "sdfsd" }, + { "alice", "vambdnkuhj" }, + { "bob", "svcdbjhws12" }, +}; +const size_t N_CREDENTIALS = sizeof(passwdfile) / sizeof(credentials_t); + +String * credentialsHandler(HTTPAuthMethod mode, String username, String params[]) +{ + for (int i = 0; i < N_CREDENTIALS; i++) { + if (username == passwdfile[i].username) + return new String(passwdfile[i].password); + } + return NULL; +} + +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(&credentialsHandler)) { + server.requestAuthentication(); + return; + } + 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(); + delay(2);//allow the cpu to switch to other tasks +} \ No newline at end of file diff --git a/libraries/WebServer/examples/HttpAuthCallbackInline/HttpAuthCallbackInline.ino b/libraries/WebServer/examples/HttpAuthCallbackInline/HttpAuthCallbackInline.ino new file mode 100644 index 00000000000..32a526b3291 --- /dev/null +++ b/libraries/WebServer/examples/HttpAuthCallbackInline/HttpAuthCallbackInline.ino @@ -0,0 +1,62 @@ +#include +#include +#include +#include + +const char* ssid = "........"; +const char* password = "........"; + +WebServer server(80); + +typedef struct credentials_t { + char * username; + char * password; +} credentials_t; + +credentials_t passwdfile[] = { + { "admin", "esp32" }, + { "fred", "41234123" }, + { "charlie", "sdfsd" }, + { "alice", "vambdnkuhj" }, + { "bob", "svcdbjhws12" }, + { NULL, NULL } +}; + +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([](HTTPAuthMethod mode, String username, String params[]) -> String * { + // Scan the password list for the username and return the password. + // + for (credentials_t * entry = passwdfile; entry->username; entry++) { + if (username == entry->username) + return new String(entry->password); + return NULL; + })) + { + server.requestAuthentication(); + return; + } + 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(); + delay(2);//allow the cpu to switch to other tasks + } \ No newline at end of file diff --git a/libraries/WebServer/examples/HttpAuthOneTimePassword/HttpAuthOneTimePassword.ino b/libraries/WebServer/examples/HttpAuthOneTimePassword/HttpAuthOneTimePassword.ino new file mode 100644 index 00000000000..3513f876166 --- /dev/null +++ b/libraries/WebServer/examples/HttpAuthOneTimePassword/HttpAuthOneTimePassword.ino @@ -0,0 +1,215 @@ +#include +#include +#include +#include +#include + +// https://github.com/dirkx/Arduino-Base32-Decode +#include + +// https://github.com/dirkx/Arduino-TOTP-RFC6238-generator +#include + +#include // for the hmac/ cookie + +#ifndef WIFI_NETWORK +#define WIFI_NETWORK "mySecretWiFiPassword" +#warning "You propably want to change this line!" +#endif + +#ifndef WIFI_PASSWD +#define WIFI_PASSWD "mySecretWiFiPassword" +#warning "You propably want to change this line!" +#endif + +#ifndef NTP_SERVER +#define NTP_SERVER "nl.pool.ntp.org" +#warning "You MUST set an appropriate ntp pool - see http://ntp.org" +#endif + +#ifndef NTP_DEFAULT_TZ +#define NTP_DEFAULT_TZ "CET-1CEST,M3.5.0,M10.5.0/3" +#endif + + +const char* ssid = WIFI_NETWORK; +const char* password = WIFI_PASSWD; + +WebServer server(80); + +String demousers[] = { "admin", "user1", "user2" }; +String demopasswords[] = { "secret", "secr!t", "s3cret" }; +String demoseeds[] = { "ORUGKU3FMNZGK5CTMVSWI", "ORUGKU3FMNZGK5CTMVSWIMQ", "ORUGKU3FMNZGK5CTMVSWIMY"}; + +const unsigned long cookie_secret_seed[2] = { esp_random(), esp_random() }; + +const int LOGIN_TIMEOUT = 30; // 30 minute timeout. +#define LOGIN_COOKIE_NAME "token" + +String hmac(time_t t) { + unsigned char digest[32]; + char digest_hex[64 + 1]; + if (mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), + (unsigned char *) & (cookie_secret_seed[0]), sizeof(cookie_secret_seed), // key + (unsigned char *) & t, sizeof(t), // input + digest)) + return ""; // safe - as we compare it with something known to be 64 hex bytes. + + for (int i = 0; i < sizeof(digest); i++) + sprintf(digest_hex + i * 2, "%02X", digest[i]); + + return String(digest_hex); +} + +// Issue 1) Once the user has logged in once; with a valid Time based OTP; that OTP will +// only be valid for up to LOGIN_TIMEOUT seconds. Then it won't work anymore. +// We solve this by setting a secure cookie that is valid for an hour. +// And if we see that - we assume all is well. +// +bool hasValidCookie(WebServer &server) { + if (!server.hasHeader("Cookie")) + return false; + + String cookie = server.header("Cookie"); + int at = cookie.indexOf(LOGIN_COOKIE_NAME "="); + if (at < 0) + return false; + + String token = cookie.substring(at + 7, 16 + 64); + if (token.length() != 16 + 64) + return false; + String time_str = token.substring(0, 16); + String hmac_str = token.substring(16, 64); + + time_t issued = strtoul(time_str.c_str(), NULL, 10); + time_t now = time(NULL); + if (now - issued > LOGIN_TIMEOUT * 60) + return false; + + return hmac(issued).equals(hmac_str); +} + +void setCookie(WebServer &server) { + char buff[16 + 64 + 1]; + snprintf(buff, sizeof(buff), "%016lu%s", time(NULL), hmac(time(NULL)).c_str()); + server.sendHeader("Set-Cookie", LOGIN_COOKIE_NAME "=" + String(buff)); +} + +String * checkOTP(HTTPAuthMethod mode, String username, String params[]) { + if (mode != BASIC_AUTH) { + Serial.println("Not basic auth - rejecting"); + return NULL; + }; + + // find the password and seed for this user. + // + String * passwd = NULL, * seed = NULL; + for (int i = 0; i < sizeof(demousers) / sizeof(String); i++) { + if (demousers[i] == username) { + passwd = &demopasswords[i]; + seed = &demoseeds[i]; + break; + } + }; + + if (!passwd || !seed) { + Serial.println("Unknown username - rejecting"); + return NULL; + }; + + // Issue 2) We anticipate some clock skew - so try 30 seconds 'around' the current time'; + // and if we're more clever; we could track which people use; and then adjust our + // idea of how much our clock is likely behind or ahead; along with that of + // people their typical clocks. + // + for (int i = -2 ; i <= 2; i++) { + String * otp = TOTP::currentOTP(*seed); //, time(NULL) + i * TOTP::RFC6238_DEFAULT_interval); + + if (!params[0].startsWith(*otp)) + next; + + // Issue 3) Use proper 2FA/MFA - and insist the user types the token followed by the password + // + if (!params[0].substring(6).equals(*passwd)) { + Serial.println("Incorrect password - rejecting"); + return NULL; + } + + // entirely `bypass' the authentication system; and return + // the password as entered; as that is known to be 'ok' + // + Serial.print("User "); Serial.print(username); Serial.println(" logged in"); + return new String(params[0]); + } // clockskew loop + + Serial.println("All OTPs wrong - rejecting"); + return NULL; +} + +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(); + + // we need a reasonable accurate time for TOTP to work. + // + configTzTime(NTP_DEFAULT_TZ, NTP_SERVER); + + server.on("/", []() { + if (!hasValidCookie(server) && !server.authenticate(&checkOTP)) { + server.requestAuthentication(); + return; + } + // set or renew the cookie. + setCookie(server); + + // Let the user in. + 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(); + static unsigned long last = millis(); + if (millis() - last < 2000) + return; + last = millis(); + time_t t = time(NULL); + if (t < 1000000) { + Serial.println("Not having a stable time yet.. TOTP is not going to work."); + return; + }; + static time_t lst_i = 0; + time_t n = t / TOTP::RFC6238_DEFAULT_interval; + if (n == lst_i) + return; + lst_i = n; + + Serial.print(ctime(&t)); + for (int i = 0; i < sizeof(demousers) / sizeof(String); i++) { + String * otp = TOTP::currentOTP(demoseeds[i]); + + Serial.print(" "); + Serial.print(demousers[i]); + Serial.print(" - "); + Serial.print(*otp); + Serial.print(demopasswords[i]); + Serial.print(" - (Seed for calculator: "); + Serial.print(demoseeds[i]); + Serial.println(")"); + + delete otp; + } +} diff --git a/libraries/WebServer/examples/HttpAuthOneTimePasswordNaive/HttpAuthOneTimePasswordNaive.ino b/libraries/WebServer/examples/HttpAuthOneTimePasswordNaive/HttpAuthOneTimePasswordNaive.ino new file mode 100644 index 00000000000..6f738c45364 --- /dev/null +++ b/libraries/WebServer/examples/HttpAuthOneTimePasswordNaive/HttpAuthOneTimePasswordNaive.ino @@ -0,0 +1,125 @@ +#include +#include +#include +#include +#include + +// https://github.com/dirkx/Arduino-Base32-Decode +#include + +// https://github.com/dirkx/Arduino-TOTP-RFC6238-generator +#include + +// Naive RFC 6238 one time password; with a couple of possibly flawed +// assumptionms: +// +// 1) we're going to assume that the clock is perfect! +// +// 2) we're going to assume you just need access for however long +// your OTP code is valid for. So that is at the most 30 seconds. +// +// 3) we're dispensing with a password; all you need to know is +// the username and the one time passcode of that moment. So this +// is not exactly MFA. +// +// Which is not very useful. But keeps this demo simple. +// + +#include // for the uint64_t typedef +#include // for the hmac/sha1 calculation + +#ifndef WIFI_NETWORK +#define WIFI_NETWORK "mySecretWiFiPassword" +#warning "You propably want to change this line!" +#endif + +#ifndef WIFI_PASSWD +#define WIFI_PASSWD "mySecretWiFiPassword" +#warning "You propably want to change this line!" +#endif + +#ifndef NTP_SERVER +#define NTP_SERVER "nl.pool.ntp.org" +#warning "You MUST set an appropriate ntp pool - see http://ntp.org" +#endif + +#ifndef NTP_DEFAULT_TZ +#define NTP_DEFAULT_TZ "CET-1CEST,M3.5.0,M10.5.0/3" +#endif + + +const char* ssid = WIFI_NETWORK; +const char* password = WIFI_PASSWD; + +WebServer server(80); + +const String demouser = "admin"; + +// we only define one seed, for the user admin. Normally every user would +// have their own seed and also enter a password - to make this proper +// two factor authentication (see note 1 below) +// +const String seed = "theSecretSeed"; // must be a Base32 string; can be == padded. +const time_t interval = 30; // seconds (default) +const time_t epoch = 0; // epoch relative to the unix epoch (jan 1970 is the default) +const int digits = 6; // length (default is 0) + +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(); + + // we need a reasonable accurate time for TOTP to work. + // + configTzTime(NTP_DEFAULT_TZ, NTP_SERVER); + + server.on("/", []() { + if (!server.authenticate([](HTTPAuthMethod mode, String username, String params[]) -> String * { + if (username == demouser) + return otp = TOTP::currentOTP(seed); + return NULL; + })) + { + server.requestAuthentication(); + return; + } + 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(); + static unsigned long last = millis(); + if (millis() - last < 2000) + return; + last = millis(); + time_t t = time(NULL); + if (t < 1000000) { + Serial.println("Not having a stable time yet.. TOTP is not going to work."); + return; + }; + static time_t lst_i = 0; + time_t n = t / interval; + if (n == lst_i) + return; + lst_i = n; + + Serial.print(ctime(&t)); + Serial.print(" TOTP at this time is: "); + String * otp = TOTP::currentOTP(seed); + Serial.println(*otp); + Serial.println(); + delete otp; +} diff --git a/libraries/WebServer/examples/HttpBasicAuthSHA1/HttpBasicAuthSHA1.ino b/libraries/WebServer/examples/HttpBasicAuthSHA1/HttpBasicAuthSHA1.ino new file mode 100644 index 00000000000..fe8a51e375c --- /dev/null +++ b/libraries/WebServer/examples/HttpBasicAuthSHA1/HttpBasicAuthSHA1.ino @@ -0,0 +1,75 @@ +#include +#include +#include +#include + +// Rather than specify the password as plaintext; we +// provide it as an (unsalted!) SHA1 hash. This is not +// much more secure (SHA1 is past its retirement age, +// and long obsolte/insecure) - but it helps a little. + +const char* ssid = "........"; +const char* password = "........"; + +WebServer server(80); + +// Passwords as plaintext - human readable and easily visible in +// the sourcecode and in the firmware/binary. +// +const char* www_username = "admin"; +const char* www_password = "esp32"; + +// The sha1 of 'esp32' (without the trailing \0) expressed as 20 +// bytes of hex. Created by for example 'echo -n esp32 | openssl sha1' +// or http://www.sha1-online.com. +// +const char* www_username_hex = "hexadmin"; +const char* www_password_hex = "8cb124f8c277c16ec0b2ee00569fd151a08e342b"; + +// The same; but now expressed as a base64 string (e.g. as commonly used +// by webservers). Created by ` echo -n esp32 | openssl sha1 -binary | openssl base64` +// +const char* www_username_base64 = "base64admin"; +const char* www_password_base64 = "jLEk+MJ3wW7Asu4AVp/RUaCONCs="; + + +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)) { + server.send(200, "text/plain", "Login against cleartext password OK"); + return; + } + if (server.authenticateBasicSHA1(www_username_hex, www_password_hex)) { + server.send(200, "text/plain", "Login against HEX of the SHA1 of the password OK"); + return; + } + if (server.authenticateBasicSHA1(www_username_base64, www_password_base64)) { + server.send(200, "text/plain", "Login against Base64 of the SHA1 of the password OK"); + return; + } + Serial.println("No/failed authentication"); + return server.requestAuthentication(); + }); + + 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(); + delay(2);//allow the cpu to switch to other tasks +} \ No newline at end of file diff --git a/libraries/WebServer/src/WebServer.cpp b/libraries/WebServer/src/WebServer.cpp index 66c01198fc9..1060c8b4d43 100644 --- a/libraries/WebServer/src/WebServer.cpp +++ b/libraries/WebServer/src/WebServer.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include "WiFiServer.h" #include "WiFiClient.h" @@ -30,7 +31,9 @@ #include "FS.h" #include "detail/RequestHandlersImpl.h" #include "mbedtls/md5.h" - +#include "mbedtls/sha1.h" +#include "mbedtls/base64.h" +#include "sodium/utils.h" static const char AUTHORIZATION_HEADER[] = "Authorization"; static const char qop_auth[] PROGMEM = "qop=auth"; @@ -138,91 +141,153 @@ static String md5str(String &in){ return String(out); } -bool WebServer::authenticate(const char * username, const char * password){ - if(hasHeader(FPSTR(AUTHORIZATION_HEADER))) { - String authReq = header(FPSTR(AUTHORIZATION_HEADER)); - if(authReq.startsWith(F("Basic"))){ - authReq = authReq.substring(6); - authReq.trim(); - char toencodeLen = strlen(username)+strlen(password)+1; - char *toencode = new char[toencodeLen + 1]; - if(toencode == NULL){ - authReq = ""; - return false; - } - char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; - if(encoded == NULL){ - authReq = ""; - delete[] toencode; - return false; - } - sprintf(toencode, "%s:%s", username, password); - if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equalsConstantTime(encoded)) { - authReq = ""; - delete[] toencode; - delete[] encoded; - return true; - } - delete[] toencode; - delete[] encoded; - } else if(authReq.startsWith(F("Digest"))) { - authReq = authReq.substring(7); - log_v("%s", authReq.c_str()); - String _username = _extractParam(authReq,F("username=\""),'\"'); - if(!_username.length() || _username != String(username)) { - authReq = ""; - return false; - } - // extracting required parameters for RFC 2069 simpler Digest - String _realm = _extractParam(authReq, F("realm=\""),'\"'); - String _nonce = _extractParam(authReq, F("nonce=\""),'\"'); - String _uri = _extractParam(authReq, F("uri=\""),'\"'); - String _response = _extractParam(authReq, F("response=\""),'\"'); - String _opaque = _extractParam(authReq, F("opaque=\""),'\"'); - - if((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_response.length()) || (!_opaque.length())) { - authReq = ""; - return false; - } - if((_opaque != _sopaque) || (_nonce != _snonce) || (_realm != _srealm)) { - authReq = ""; - return false; - } - // parameters for the RFC 2617 newer Digest - String _nc,_cnonce; - if(authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) { - _nc = _extractParam(authReq, F("nc="), ','); - _cnonce = _extractParam(authReq, F("cnonce=\""),'\"'); - } - String _H1 = md5str(String(username) + ':' + _realm + ':' + String(password)); - log_v("Hash of user:realm:pass=%s", _H1.c_str()); - String _H2 = ""; - if(_currentMethod == HTTP_GET){ - _H2 = md5str(String(F("GET:")) + _uri); - }else if(_currentMethod == HTTP_POST){ - _H2 = md5str(String(F("POST:")) + _uri); - }else if(_currentMethod == HTTP_PUT){ - _H2 = md5str(String(F("PUT:")) + _uri); - }else if(_currentMethod == HTTP_DELETE){ - _H2 = md5str(String(F("DELETE:")) + _uri); - }else{ - _H2 = md5str(String(F("GET:")) + _uri); - } - log_v("Hash of GET:uri=%s", _H2.c_str()); - String _responsecheck = ""; - if(authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) { - _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2); - } else { - _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2); - } - log_v("The Proper response=%s", _responsecheck.c_str()); - if(_response == _responsecheck){ - authReq = ""; - return true; - } +bool WebServer::authenticateBasicSHA1(const char * _username, const char * _sha1Base64orHex) { + return WebServer::authenticate([_username,_sha1Base64orHex](HTTPAuthMethod mode, String username, String params[]) -> String * { + // rather than work on a password to compare with; we take the sha1 of the + // password received over the wire and compare that to the base64 encoded + // sha1 passed as _sha1base64. That way there is no need to have a + // plaintext password in the code/binary (though note that SHA1 is well + // past its retirement age). When that matches - we `cheat' by returning + // the password we got in the first place; so the normal BasicAuth + // can be completed. Note that this cannot work for a digest auth - + // as there the password in the clear is part of the calculation. + uint8_t sha1[20]; + char sha1calc[48]; // large enough for base64 and Hex represenation + size_t olen = 0; + int ret = 0; + + mbedtls_sha1((const uint8_t*) params[0].c_str(),params[0].length(),sha1); + + // we can either decode _sha1base64orHex and then compare the 20 bytes; + // or encode the sha we calculated. We pick the latter as encoding of a + // fixed array of 20 bytes s safer than operating on something external. + // + if (strlen(_sha1Base64orHex) == 20 * 2) + sodium_bin2hex(sha1calc, sizeof(sha1calc), sha1, sizeof(sha1)); + else + ret = mbedtls_base64_encode((uint8_t*)sha1calc, sizeof(sha1calc), &olen, sha1, sizeof(sha1)); + + return ((username.equalsConstantTime(_username)) && + (String((char*)sha1calc).equalsConstantTime(_sha1Base64orHex)) && + (ret == 0) && + (mode == BASIC_AUTH) /* to keep things somewhat time constant. */ + ) ? new String(params[0]) : NULL; + }); +} + +bool WebServer::authenticate(const char * _username, const char * _password){ + return WebServer::authenticate([_username,_password](HTTPAuthMethod mode, String username, String params[]) -> String * { + return username.equalsConstantTime(_username) ? new String(_password) : NULL; + }); +} + +bool WebServer::authenticate(THandlerFunctionAuthCheck fn) { + if(!hasHeader(FPSTR(AUTHORIZATION_HEADER))) + return false; + + String authReq = header(FPSTR(AUTHORIZATION_HEADER)); + if(authReq.startsWith(AuthTypeBasic)) { + bool ret = false; + + authReq = authReq.substring(6); // length of AuthTypeBasic including the space at the end. + authReq.trim(); + + /* base64 encoded string is always shorter (or equal) in length */ + char *decoded = (authReq.length() < HTTP_MAX_BASIC_AUTH_LEN) ? new char[authReq.length()] : NULL; + if (decoded) { + char * p; + if (base64_decode_chars(authReq.c_str(), authReq.length(), decoded) && (p = index(decoded,':')) && p) { + authReq = ""; + /* Note: rfc7617 guarantees that there will not be an escaped colon in the username itself. + * Note: base64_decode_chars() guarantees a terminating \0 + */ + *p = '\0'; + char * _username = decoded, *_password = p+1; + String params[] = { _password, _srealm }; + String * password = fn(BASIC_AUTH, _username, params); + if (password) { + ret = password->equalsConstantTime(_password); + // we're more concerned about the password; as the attacker already + // knows the _pasword. Arduino's string handling is simple; it reallocs + // even when smaller; so a memset is enough (no capacity/size). + memset((void*)password->c_str(),0,password->length()); + delete password; + }; + }; + delete decoded; + }; + authReq = ""; + return ret; + } else if(authReq.startsWith(AuthTypeDigest)) { + authReq = authReq.substring(7); + log_v("%s", authReq.c_str()); + + // extracting required parameters for RFC 2069 simpler Digest + // + String _username = _extractParam(authReq,F("username=\""),'\"'); + String _realm = _extractParam(authReq, F("realm=\""),'\"'); + String _uri = _extractParam(authReq, F("uri=\""),'\"'); + if (!_username.length()) + goto exf; + + String params[] = { _realm, _uri }; + String * password = fn(DIGEST_AUTH,_username, params); + if(!password) + goto exf; + + String _H1 = md5str(String(_username) + ':' + _realm + ':' + *password); + // we're extra concerned; as digest request us to know the password + // in the clear. + memset((void*)password->c_str(),0,password->length()); + delete password; + _username = ""; + + String _nonce = _extractParam(authReq, F("nonce=\""),'\"'); + String _response = _extractParam(authReq, F("response=\""),'\"'); + String _opaque = _extractParam(authReq, F("opaque=\""),'\"'); + + if((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_response.length()) || (!_opaque.length())) + goto exf; + + if((_opaque != _sopaque) || (_nonce != _snonce) || (_realm != _srealm)) + goto exf; + + // parameters for the RFC 2617 newer Digest + // + String _nc,_cnonce; + if(authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) { + _nc = _extractParam(authReq, F("nc="), ','); + _cnonce = _extractParam(authReq, F("cnonce=\""),'\"'); + } + + log_v("Hash of user:realm:pass=%s", _H1.c_str()); + String _H2 = ""; + if(_currentMethod == HTTP_GET){ + _H2 = md5str(String(F("GET:")) + _uri); + }else if(_currentMethod == HTTP_POST){ + _H2 = md5str(String(F("POST:")) + _uri); + }else if(_currentMethod == HTTP_PUT){ + _H2 = md5str(String(F("PUT:")) + _uri); + }else if(_currentMethod == HTTP_DELETE){ + _H2 = md5str(String(F("DELETE:")) + _uri); + }else{ + _H2 = md5str(String(F("GET:")) + _uri); + } + log_v("Hash of GET:uri=%s", _H2.c_str()); + String _responsecheck = ""; + if(authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) { + _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2); + } else { + _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2); } authReq = ""; + + log_v("The Proper response=%s", _responsecheck.c_str()); + return _response == _responsecheck; } + +exf: + authReq = ""; return false; } @@ -242,11 +307,11 @@ void WebServer::requestAuthentication(HTTPAuthMethod mode, const char* realm, co _srealm = String(realm); } if(mode == BASIC_AUTH) { - sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Basic realm=\"")) + _srealm + String(F("\""))); + sendHeader(String(FPSTR(WWW_Authenticate)), AuthTypeBasic + String(F(" realm=\"")) + _srealm + String(F("\""))); } else { _snonce=_getRandomHexString(); _sopaque=_getRandomHexString(); - sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Digest realm=\"")) +_srealm + String(F("\", qop=\"auth\", nonce=\"")) + _snonce + String(F("\", opaque=\"")) + _sopaque + String(F("\""))); + sendHeader(String(FPSTR(WWW_Authenticate)), AuthTypeDigest + String(F(" realm=\"")) +_srealm + String(F("\", qop=\"auth\", nonce=\"")) + _snonce + String(F("\", opaque=\"")) + _sopaque + String(F("\""))); } using namespace mime; send(401, String(FPSTR(mimeTable[html].mimeType)), authFailMsg); diff --git a/libraries/WebServer/src/WebServer.h b/libraries/WebServer/src/WebServer.h index fc60d16496f..25c6fcae308 100644 --- a/libraries/WebServer/src/WebServer.h +++ b/libraries/WebServer/src/WebServer.h @@ -45,6 +45,7 @@ enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH }; #define HTTP_MAX_POST_WAIT 5000 //ms to wait for POST data to arrive #define HTTP_MAX_SEND_WAIT 5000 //ms to wait for data chunk to be ACKed #define HTTP_MAX_CLOSE_WAIT 2000 //ms to wait for the client to close the connection +#define HTTP_MAX_BASIC_AUTH_LEN 256 // maximum length of a basic Auth base64 encoded username:password string #define CONTENT_LENGTH_UNKNOWN ((size_t) -1) #define CONTENT_LENGTH_NOT_SET ((size_t) -2) @@ -81,7 +82,15 @@ class WebServer virtual void close(); void stop(); + const String AuthTypeDigest = F("Digest"); + const String AuthTypeBasic = F("Basic"); + + typedef std::function THandlerFunctionAuthCheck; + + bool authenticate(THandlerFunctionAuthCheck fn); bool authenticate(const char * username, const char * password); + bool authenticateBasicSHA1(const char * _username, const char * _sha1AsBase64orHex); + void requestAuthentication(HTTPAuthMethod mode = BASIC_AUTH, const char* realm = NULL, const String& authFailMsg = String("") ); typedef std::function THandlerFunction; From df0c1894dc52139c07ff8a9bf1eabf7b64d11140 Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Fri, 4 Nov 2022 21:38:03 +0100 Subject: [PATCH 02/21] Tweaks after testing against docs and latest Library tree --- .../HttpAuthCallbackInline.ino | 52 +++++---- .../HttpAuthOneTimePassword.ino | 18 ++- .../HttpAuthOneTimePasswordNaive.ino | 14 ++- .../HttpBasicAuthSHA1orBearerToken.ino | 106 ++++++++++++++++++ libraries/WebServer/src/WebServer.cpp | 5 +- libraries/WebServer/src/WebServer.h | 2 +- 6 files changed, 161 insertions(+), 36 deletions(-) create mode 100644 libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino diff --git a/libraries/WebServer/examples/HttpAuthCallbackInline/HttpAuthCallbackInline.ino b/libraries/WebServer/examples/HttpAuthCallbackInline/HttpAuthCallbackInline.ino index 32a526b3291..990207a0aea 100644 --- a/libraries/WebServer/examples/HttpAuthCallbackInline/HttpAuthCallbackInline.ino +++ b/libraries/WebServer/examples/HttpAuthCallbackInline/HttpAuthCallbackInline.ino @@ -35,28 +35,32 @@ void setup() { server.on("/", []() { if (!server.authenticate([](HTTPAuthMethod mode, String username, String params[]) -> String * { - // Scan the password list for the username and return the password. - // - for (credentials_t * entry = passwdfile; entry->username; entry++) { - if (username == entry->username) - return new String(entry->password); - return NULL; - })) - { - server.requestAuthentication(); - return; - } - 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"); - } + // Scan the password list for the username and return the password if + // we find the username. + // + for (credentials_t * entry = passwdfile; entry->username; entry++) { + if (username == entry->username) { + return new String(entry->password); + }; + }; + // we've not found the user in the list. + return NULL; + })) + { + server.requestAuthentication(); + return; + } + 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(); - delay(2);//allow the cpu to switch to other tasks - } \ No newline at end of file +void loop() { + ArduinoOTA.handle(); + server.handleClient(); + delay(2);//allow the cpu to switch to other tasks +} \ No newline at end of file diff --git a/libraries/WebServer/examples/HttpAuthOneTimePassword/HttpAuthOneTimePassword.ino b/libraries/WebServer/examples/HttpAuthOneTimePassword/HttpAuthOneTimePassword.ino index 3513f876166..57cb07c5cdb 100644 --- a/libraries/WebServer/examples/HttpAuthOneTimePassword/HttpAuthOneTimePassword.ino +++ b/libraries/WebServer/examples/HttpAuthOneTimePassword/HttpAuthOneTimePassword.ino @@ -4,11 +4,17 @@ #include #include -// https://github.com/dirkx/Arduino-Base32-Decode +// https://github.com/dirkx/Arduino-Base32-Decode or simply from +// Sketch->Include Library->Library manager and search for +// "Base32-Decode". +// #include -// https://github.com/dirkx/Arduino-TOTP-RFC6238-generator -#include +// https://github.com/dirkx/Arduino-TOTP-RFC6238-generator or simply from +// Sketch->Include Library->Library manager and search for +// "TOTP-RC6236-generator". +// +#include #include // for the hmac/ cookie @@ -123,10 +129,10 @@ String * checkOTP(HTTPAuthMethod mode, String username, String params[]) { // people their typical clocks. // for (int i = -2 ; i <= 2; i++) { - String * otp = TOTP::currentOTP(*seed); //, time(NULL) + i * TOTP::RFC6238_DEFAULT_interval); + String * otp = TOTP::currentOTP(time(NULL) + i * TOTP::RFC6238_DEFAULT_interval, *seed); if (!params[0].startsWith(*otp)) - next; + continue; // Issue 3) Use proper 2FA/MFA - and insist the user types the token followed by the password // @@ -212,4 +218,4 @@ void loop() { delete otp; } -} +} \ No newline at end of file diff --git a/libraries/WebServer/examples/HttpAuthOneTimePasswordNaive/HttpAuthOneTimePasswordNaive.ino b/libraries/WebServer/examples/HttpAuthOneTimePasswordNaive/HttpAuthOneTimePasswordNaive.ino index 6f738c45364..41f4a222388 100644 --- a/libraries/WebServer/examples/HttpAuthOneTimePasswordNaive/HttpAuthOneTimePasswordNaive.ino +++ b/libraries/WebServer/examples/HttpAuthOneTimePasswordNaive/HttpAuthOneTimePasswordNaive.ino @@ -4,11 +4,17 @@ #include #include -// https://github.com/dirkx/Arduino-Base32-Decode +// https://github.com/dirkx/Arduino-Base32-Decode or simply from +// Sketch->Include Library->Library manager and search for +// "Base32-Decode". +// #include -// https://github.com/dirkx/Arduino-TOTP-RFC6238-generator -#include +// https://github.com/dirkx/Arduino-TOTP-RFC6238-generator or simply from +// Sketch->Include Library->Library manager and search for +// "TOTP-RC6236-generator". +// +#include // Naive RFC 6238 one time password; with a couple of possibly flawed // assumptionms: @@ -82,7 +88,7 @@ void setup() { server.on("/", []() { if (!server.authenticate([](HTTPAuthMethod mode, String username, String params[]) -> String * { if (username == demouser) - return otp = TOTP::currentOTP(seed); + return TOTP::currentOTP(seed); return NULL; })) { diff --git a/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino b/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino new file mode 100644 index 00000000000..9d43732a584 --- /dev/null +++ b/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino @@ -0,0 +1,106 @@ +#include +#include +#include +#include +#include "mbedtls/sha1.h" +#include "sodium/utils.h" + + +/// We have two options - we either come in with a bearer +// token - i.e. a special header or API token; or we +// get a normal HTTP style basic auth prompt. +// +// To do a bearer fetch - use something like Swagger or with curl: +// +// curl https://myesp.com/ -H "Authorization: Bearer SecritToken" +// +// We avoid hardcoding this "SecritToken" into the code by +// using a SHA1 instead (which is not paricularly secure). + +// Create the secet token SHA1 with: +// echo -n SecritToken | openssl sha1 +// +String secret_token_hex = "d2cce6b472959484a21c3194080c609b8a2c910b"; + +// Wifi credentials +// +const char* ssid = "........"; +const char* password = "........"; + +WebServer server(80); + +// Rather than specify the admin password as plaintext; we +// provide it as an (unsalted!) SHA1 hash. This is not +// much more secure (SHA1 is past its retirement age, +// and long obsolte/insecure) - but it helps a little. + +// The sha1 of 'esp32' (without the trailing \0) expressed as 20 +// bytes of hex. Created by for example 'echo -n esp32 | openssl sha1' +// or http://www.sha1-online.com. +// +const char* www_username_hex = "hexadmin"; +const char* www_password_hex = "8cb124f8c277c16ec0b2ee00569fd151a08e342b"; + +// The same; but now expressed as a base64 string (e.g. as commonly used +// by webservers). Created by ` echo -n esp32 | openssl sha1 -binary | openssl base64` +// +const char* www_username_base64 = "base64admin"; +const char* www_password_base64 = "jLEk+MJ3wW7Asu4AVp/RUaCONCs="; + +String * check_bearer_or_auth(HTTPAuthMethod mode, String authReq, String params[]) { + // we expect authReq to be "bearer some-secret" + // + String lcAuthReq = authReq; + lcAuthReq.toLowerCase(); + if (mode == OTHER_AUTH && (lcAuthReq.startsWith("bearer "))) { + String secret = authReq.substring(7); + secret.trim(); + + uint8_t sha1[20]; + mbedtls_sha1((const uint8_t*) secret.c_str(), secret.length(), sha1); + + char sha1calc[48]; // large enough for base64 and Hex represenation + sodium_bin2hex(sha1calc, sizeof(sha1calc), sha1, sizeof(sha1)); + + if (secret_token_hex.equalsConstantTime(sha1calc)) + return new String("anything non null"); + }; + + // that failed - so do a normal auth + // + return server.authenticateBasicSHA1(www_username_hex, www_password_hex) ? + new String(params[0]) : NULL; +}; + +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(&check_bearer_or_auth)) { + Serial.println("No/failed authentication"); + return server.requestAuthentication(); + } + server.send(200, "text/plain", "Login okOK"); + return; + }); + + 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(); + delay(2);//allow the cpu to switch to other tasks +} \ No newline at end of file diff --git a/libraries/WebServer/src/WebServer.cpp b/libraries/WebServer/src/WebServer.cpp index 1060c8b4d43..ab3eea83b1a 100644 --- a/libraries/WebServer/src/WebServer.cpp +++ b/libraries/WebServer/src/WebServer.cpp @@ -284,8 +284,11 @@ bool WebServer::authenticate(THandlerFunctionAuthCheck fn) { log_v("The Proper response=%s", _responsecheck.c_str()); return _response == _responsecheck; + } else if (authReq.length()) { + String * ret = fn(OTHER_AUTH, authReq, {}); + if (ret) + return true; } - exf: authReq = ""; return false; diff --git a/libraries/WebServer/src/WebServer.h b/libraries/WebServer/src/WebServer.h index 25c6fcae308..22bd2fab0f2 100644 --- a/libraries/WebServer/src/WebServer.h +++ b/libraries/WebServer/src/WebServer.h @@ -33,7 +33,7 @@ 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 }; +enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH, OTHER_AUTH }; #define HTTP_DOWNLOAD_UNIT_SIZE 1436 From 09a982e889e3bcc9ddcd30544c021c42ce764e01 Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Fri, 4 Nov 2022 21:49:15 +0100 Subject: [PATCH 03/21] Add documentatin for callback handler --- libraries/WebServer/src/WebServer.h | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/libraries/WebServer/src/WebServer.h b/libraries/WebServer/src/WebServer.h index 22bd2fab0f2..f467cf4feb5 100644 --- a/libraries/WebServer/src/WebServer.h +++ b/libraries/WebServer/src/WebServer.h @@ -85,7 +85,28 @@ class WebServer const String AuthTypeDigest = F("Digest"); const String AuthTypeBasic = F("Basic"); - typedef std::function THandlerFunctionAuthCheck; + /* Callbackhandler for authentication. The extra parameters depend on the + * HTTPAuthMethod mode: + * + * BASIC_AUTH enteredUsernameOrReq contains the username entered by the user + * param[0] password entered (in the clear) + * param[1] authentication realm. + * + * To return - the password the user entered password is compared to. Or Null on fail. + * + * DIGEST_AUTH enteredUsernameOrReq contains the username entered by the user + * param[0] autenticaiton realm + * param[1] authentication URI + * + * To return - the password of which the digest will be based on for comparison. Or NULL + * to fail. + * + * OTHER_AUTH enteredUsernameOrReq rest of the auth line. + * params empty array + * + * To return - NULL to fail; or any string. + */ + typedef std::function THandlerFunctionAuthCheck; bool authenticate(THandlerFunctionAuthCheck fn); bool authenticate(const char * username, const char * password); From 15f4da7bd95efa569c2be6b1868f3214b8e55907 Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Fri, 4 Nov 2022 22:03:02 +0100 Subject: [PATCH 04/21] Bodge to allow things to compile without the dependencies --- .../HttpAuthOneTimePassword/HttpAuthOneTimePassword.ino | 8 +++++++- .../HttpAuthOneTimePasswordNaive.ino | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/libraries/WebServer/examples/HttpAuthOneTimePassword/HttpAuthOneTimePassword.ino b/libraries/WebServer/examples/HttpAuthOneTimePassword/HttpAuthOneTimePassword.ino index 57cb07c5cdb..0cb03cc2825 100644 --- a/libraries/WebServer/examples/HttpAuthOneTimePassword/HttpAuthOneTimePassword.ino +++ b/libraries/WebServer/examples/HttpAuthOneTimePassword/HttpAuthOneTimePassword.ino @@ -1,3 +1,8 @@ +#if 1 +// bodge to get this example to compile in the default install. +void setup() {}; +void loop() {}; +#else #include #include #include @@ -218,4 +223,5 @@ void loop() { delete otp; } -} \ No newline at end of file +} +#endif diff --git a/libraries/WebServer/examples/HttpAuthOneTimePasswordNaive/HttpAuthOneTimePasswordNaive.ino b/libraries/WebServer/examples/HttpAuthOneTimePasswordNaive/HttpAuthOneTimePasswordNaive.ino index 41f4a222388..efe2975119f 100644 --- a/libraries/WebServer/examples/HttpAuthOneTimePasswordNaive/HttpAuthOneTimePasswordNaive.ino +++ b/libraries/WebServer/examples/HttpAuthOneTimePasswordNaive/HttpAuthOneTimePasswordNaive.ino @@ -1,3 +1,8 @@ +#if 1 +// bodge to get this example to compile in the default install. +void setup() {}; +void loop() {}; +#else #include #include #include @@ -129,3 +134,4 @@ void loop() { Serial.println(); delete otp; } +#endif From 48814ad1ccb430ed3be0678c201bf9eca8ffc77f Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Fri, 4 Nov 2022 22:40:00 +0100 Subject: [PATCH 05/21] Remove dependency on sodium to make it compile with 4.4 --- .../HttpBasicAuthSHA1orBearerToken.ino | 33 +++++++++++++------ libraries/WebServer/src/WebServer.cpp | 14 +++++--- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino b/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino index 9d43732a584..bd08bf68287 100644 --- a/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino +++ b/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino @@ -3,7 +3,6 @@ #include #include #include "mbedtls/sha1.h" -#include "sodium/utils.h" /// We have two options - we either come in with a bearer @@ -47,11 +46,12 @@ const char* www_password_hex = "8cb124f8c277c16ec0b2ee00569fd151a08e342b"; const char* www_username_base64 = "base64admin"; const char* www_password_base64 = "jLEk+MJ3wW7Asu4AVp/RUaCONCs="; +static unsigned char _bearer[20]; String * check_bearer_or_auth(HTTPAuthMethod mode, String authReq, String params[]) { // we expect authReq to be "bearer some-secret" // String lcAuthReq = authReq; - lcAuthReq.toLowerCase(); + lcAuthReq.toLowerCase(); if (mode == OTHER_AUTH && (lcAuthReq.startsWith("bearer "))) { String secret = authReq.substring(7); secret.trim(); @@ -59,16 +59,13 @@ String * check_bearer_or_auth(HTTPAuthMethod mode, String authReq, String params uint8_t sha1[20]; mbedtls_sha1((const uint8_t*) secret.c_str(), secret.length(), sha1); - char sha1calc[48]; // large enough for base64 and Hex represenation - sodium_bin2hex(sha1calc, sizeof(sha1calc), sha1, sizeof(sha1)); - - if (secret_token_hex.equalsConstantTime(sha1calc)) - return new String("anything non null"); + if (0 == memcpy(_bearer, sha1, sizeof(_bearer))) + return new String("anything non null"); }; - // that failed - so do a normal auth - // - return server.authenticateBasicSHA1(www_username_hex, www_password_hex) ? +// that failed - so do a normal auth +// +return server.authenticateBasicSHA1(www_username_hex, www_password_hex) ? new String(params[0]) : NULL; }; @@ -83,6 +80,22 @@ void setup() { } ArduinoOTA.begin(); + + // Convert token to a convenient binary representation. + // + if (secret_token_hex.length() != 2 * 20) { + Serial.println("Bearer token does not look like a hex string ?!"); + } + + #define _H2D(x) (((x)>='0' && ((x) <='9')) ? ((x)-'0') : (((x)>='a' && (x)<='f') ? ((x)-'a') : 0)) + #define H2D(x) (_H2D(tolower((x)))) + const char * _shaBase64 = secret_token_hex.c_str(); + for (int i = 0; i < 20; i++) { + unsigned char c = _shaBase64[2 * i + 0]; + unsigned char d = _shaBase64[2 * i + 1]; + _bearer[i] = (H2D(c) << 4) | H2D(d); + }; + server.on("/", []() { if (!server.authenticate(&check_bearer_or_auth)) { Serial.println("No/failed authentication"); diff --git a/libraries/WebServer/src/WebServer.cpp b/libraries/WebServer/src/WebServer.cpp index ab3eea83b1a..406acd6f23f 100644 --- a/libraries/WebServer/src/WebServer.cpp +++ b/libraries/WebServer/src/WebServer.cpp @@ -33,7 +33,6 @@ #include "mbedtls/md5.h" #include "mbedtls/sha1.h" #include "mbedtls/base64.h" -#include "sodium/utils.h" static const char AUTHORIZATION_HEADER[] = "Authorization"; static const char qop_auth[] PROGMEM = "qop=auth"; @@ -162,9 +161,16 @@ bool WebServer::authenticateBasicSHA1(const char * _username, const char * _sha1 // or encode the sha we calculated. We pick the latter as encoding of a // fixed array of 20 bytes s safer than operating on something external. // - if (strlen(_sha1Base64orHex) == 20 * 2) - sodium_bin2hex(sha1calc, sizeof(sha1calc), sha1, sizeof(sha1)); - else + #define _H2D(x) (((x)>='0' && ((x) <='9')) ? ((x)-'0') : (((x)>='a' && (x)<='f') ? ((x)-'a') : 0)) + #define H2D(x) (_H2D(tolower((x)))) + if (strlen(_sha1Base64orHex) == 20 * 2) { + for(int i = 0; i < 20; i++) { + unsigned char c = _sha1Base64orHex[2*i]; + unsigned char d = _sha1Base64orHex[2*i+1]; + sha1calc[i] = (H2D(c)<<4) | H2D(d); + }; + ret = 0; + } else ret = mbedtls_base64_encode((uint8_t*)sha1calc, sizeof(sha1calc), &olen, sha1, sizeof(sha1)); return ((username.equalsConstantTime(_username)) && From f1282fc79fe7885acaa27f24c3fdd9e1177efeee Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Fri, 4 Nov 2022 22:42:45 +0100 Subject: [PATCH 06/21] Fix hex conversion --- .../HttpBasicAuthSHA1orBearerToken.ino | 4 ++-- libraries/WebServer/src/WebServer.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino b/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino index bd08bf68287..1e2014f1ee4 100644 --- a/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino +++ b/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino @@ -87,7 +87,7 @@ void setup() { Serial.println("Bearer token does not look like a hex string ?!"); } - #define _H2D(x) (((x)>='0' && ((x) <='9')) ? ((x)-'0') : (((x)>='a' && (x)<='f') ? ((x)-'a') : 0)) + #define _H2D(x) (((x)>='0' && ((x) <='9')) ? ((x)-'0') : (((x)>='a' && (x)<='f') ? ((x)-'a'+10) : 0)) #define H2D(x) (_H2D(tolower((x)))) const char * _shaBase64 = secret_token_hex.c_str(); for (int i = 0; i < 20; i++) { @@ -116,4 +116,4 @@ void loop() { ArduinoOTA.handle(); server.handleClient(); delay(2);//allow the cpu to switch to other tasks -} \ No newline at end of file +} diff --git a/libraries/WebServer/src/WebServer.cpp b/libraries/WebServer/src/WebServer.cpp index 406acd6f23f..57ad47360b5 100644 --- a/libraries/WebServer/src/WebServer.cpp +++ b/libraries/WebServer/src/WebServer.cpp @@ -161,7 +161,7 @@ bool WebServer::authenticateBasicSHA1(const char * _username, const char * _sha1 // or encode the sha we calculated. We pick the latter as encoding of a // fixed array of 20 bytes s safer than operating on something external. // - #define _H2D(x) (((x)>='0' && ((x) <='9')) ? ((x)-'0') : (((x)>='a' && (x)<='f') ? ((x)-'a') : 0)) + #define _H2D(x) (((x)>='0' && ((x) <='9')) ? ((x)-'0') : (((x)>='a' && (x)<='f') ? ((x)-'a'+10) : 0)) #define H2D(x) (_H2D(tolower((x)))) if (strlen(_sha1Base64orHex) == 20 * 2) { for(int i = 0; i < 20; i++) { From 30b08d61f5e2356c11923ea007c9e78f98138e71 Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Sat, 5 Nov 2022 20:17:48 +0100 Subject: [PATCH 07/21] Move some common HEX functions into a static HEX class, remove those from MD5 and add some examples. This allows for the cleanup of various to/from HEX routines elsewhere. --- cores/esp32/HEXBuilder.cpp | 69 +++++++++++++ cores/esp32/HEXBuilder.h | 33 +++++++ cores/esp32/MD5Builder.cpp | 20 +--- cores/esp32/MD5Builder.h | 4 +- .../ESP32/examples/HEXBuilder/HEXBuilder.ino | 76 +++++++++++++++ .../ESP32/examples/MD5Builder/MD5Builder.ino | 97 +++++++++++++++++++ 6 files changed, 282 insertions(+), 17 deletions(-) create mode 100644 cores/esp32/HEXBuilder.cpp create mode 100644 cores/esp32/HEXBuilder.h create mode 100644 libraries/ESP32/examples/HEXBuilder/HEXBuilder.ino create mode 100644 libraries/ESP32/examples/MD5Builder/MD5Builder.ino diff --git a/cores/esp32/HEXBuilder.cpp b/cores/esp32/HEXBuilder.cpp new file mode 100644 index 00000000000..7c456409798 --- /dev/null +++ b/cores/esp32/HEXBuilder.cpp @@ -0,0 +1,69 @@ +/* + Copyright (c) 2015 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include +#include + +static uint8_t hex_char_to_byte(uint8_t c) +{ + return (c >= 'a' && c <= 'f') ? (c - ((uint8_t)'a' - 0xa)) : + (c >= 'A' && c <= 'F') ? (c - ((uint8_t)'A' - 0xA)) : + (c >= '0' && c<= '9') ? (c - (uint8_t)'0') : 0x10; // unknown char is 16 +} + +size_t HEXBuilder::hex2bytes(unsigned char * out, size_t maxlen, String &in) { + return hex2bytes(out, maxlen, in.c_str()); +} + +size_t HEXBuilder::hex2bytes(unsigned char * out, size_t maxlen, const char * in) { + size_t len = 0; + for(;*in;in++) { + uint8_t c = hex_char_to_byte(*in); + // Silently skip anything unknown. + if (c > 15) + continue; + + if (len & 1) { + if (len/2 < maxlen) + out[len/2] |= c; + } else { + if (len/2 < maxlen) + out[len/2] = c<<4; + } + len++; + } + return (len + 1)/2; +} + +size_t HEXBuilder::bytes2hex(char * out, size_t maxlen, const unsigned char * in, size_t len) { + for(size_t i = 0; i < len; i++) + if (i*2 + 1 < maxlen) + sprintf(out + (i * 2), "%02x", in[i]); + + return len * 2 + 1; +} + +String HEXBuilder::bytes2hex(const unsigned char * in, size_t len) { + size_t maxlen = len * 2 + 1; + char * out = (char *) malloc(maxlen); + if (!out) return String(); + bytes2hex(out, maxlen, in, len); + String ret = String(out); + free(out); + return ret; +} diff --git a/cores/esp32/HEXBuilder.h b/cores/esp32/HEXBuilder.h new file mode 100644 index 00000000000..0bc2353ec45 --- /dev/null +++ b/cores/esp32/HEXBuilder.h @@ -0,0 +1,33 @@ +/* + Copyright (c) 2015 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef __ESP8266_HEX_BUILDER__ +#define __ESP8266_HEX_BUILDER__ + +#include +#include + +class HEXBuilder { +public: + static size_t hex2bytes(unsigned char * out, size_t maxlen, String & in); + static size_t hex2bytes(unsigned char * out, size_t maxlen, const char * in); + + static String bytes2hex(const unsigned char * in, size_t len); + static size_t bytes2hex(char * out, size_t maxlen, const unsigned char * in, size_t len); +}; +#endif diff --git a/cores/esp32/MD5Builder.cpp b/cores/esp32/MD5Builder.cpp index c988abd1e05..ef245c4ece1 100644 --- a/cores/esp32/MD5Builder.cpp +++ b/cores/esp32/MD5Builder.cpp @@ -17,15 +17,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include +#include #include -static uint8_t hex_char_to_byte(uint8_t c) -{ - return (c >= 'a' && c <= 'f') ? (c - ((uint8_t)'a' - 0xa)) : - (c >= 'A' && c <= 'F') ? (c - ((uint8_t)'A' - 0xA)) : - (c >= '0' && c<= '9') ? (c - (uint8_t)'0') : 0; -} - void MD5Builder::begin(void) { memset(_buf, 0x00, ESP_ROM_MD5_DIGEST_LEN); @@ -39,16 +33,12 @@ void MD5Builder::add(uint8_t * data, uint16_t len) void MD5Builder::addHexString(const char * data) { - uint16_t i, len = strlen(data); + uint16_t len = strlen(data); uint8_t * tmp = (uint8_t*)malloc(len/2); if(tmp == NULL) { return; } - for(i=0; i + +void setup() { + Serial.begin(115200); + delay(100); + Serial.println("\n\n\nStart."); + + // Convert a HEX string like 6c6c6f20576f726c64 to a binary buffer + // + { + const char * out = "Hello World"; + const char * hexin = "48656c6c6f20576f726c6400"; // As the string above is \0 terminated too + + unsigned char buff[256]; + size_t len = HEXBuilder::hex2bytes(buff, sizeof(buff), hexin); + + if (len != 1 + strlen(out)) + Serial.println("Odd - length 1 is wrong"); + + if (memcmp(buff, out, len) != 0) + Serial.println("Odd - decode 1 went wrong"); + + // Safe to print this binary buffer -- as we've included a \0 in the hex sequence. + // + Serial.printf("IN: <%s>\nOUT <%s\\0>\n", hexin, buff); + }; + + { + String helloHEX = "48656c6c6f20576f726c64"; + const char hello[] = "Hello World"; + + unsigned char buff[256]; + size_t len = HEXBuilder::hex2bytes(buff, sizeof(buff), helloHEX); + + if (len != strlen(hello)) + Serial.println("Odd - length 2 is wrong"); + + if (strcmp((char *) buff, hello) != 0) + Serial.println("Odd - decode 2 went wrong"); + } + + { + const unsigned char helloBytes[] = { 0x48, 0x56, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64 }; + String helloHEX = "48566c6c6f20576f726c64"; + + + String out = HEXBuilder::bytes2hex(helloBytes, sizeof(helloBytes)); + if (out.length() != 2 * sizeof(helloBytes)) + Serial.println("Odd - length 3 is wrong"); + + // we need to ignore case - as a hex string can be spelled in uppercase and lowercase + // + if (!out.equalsIgnoreCase(helloHEX)) { + Serial.println("Odd - decode 3 went wrong"); + } + } + + { + const unsigned char helloBytes[] = { 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64 }; + const char helloHex[] = "6c6c6f20576f726c64"; + + char buff[256]; + size_t len = HEXBuilder::bytes2hex(buff, sizeof(buff), helloBytes, sizeof(helloBytes)); + if (len != 1 + 2 * sizeof(helloBytes)) + Serial.println("Odd - length 4 is wrong"); + + // we need to ignore case - as a hex string can be spelled in uppercase and lowercase + // + if (strcasecmp(buff, helloHex)) + Serial.println("Odd - decode 4 went wrong"); + } + Serial.println("Done."); +} + +void loop() { +} diff --git a/libraries/ESP32/examples/MD5Builder/MD5Builder.ino b/libraries/ESP32/examples/MD5Builder/MD5Builder.ino new file mode 100644 index 00000000000..d6842e84b60 --- /dev/null +++ b/libraries/ESP32/examples/MD5Builder/MD5Builder.ino @@ -0,0 +1,97 @@ +#include + +// Occasionally it is useful to compare a password that the user +// has entered to a build in string. However this means that the +// password ends up `in the clear' in the firmware and in your +// source code. +// +// MD5Builder helps you obfuscate this (it is not terribly secure, MD5 +// has been phased out as insecure eons ago) by letting you create an +// MD5 of the data the user entered; and then compare this to an MD5 +// string that you have put in your code. +// +void setup() { + Serial.begin(115200); + delay(100); + Serial.println("\n\n\nStart."); + + // Check if a password obfuscated in an MD5 actually + // matches the original string. + // + // echo -n "Hello World" | openssl md5 + // + { + String md5 = "b10a8db164e0754105b7a99be72e3fe5"; + String password = "Hello World"; + + MD5Builder md; + + md.begin(); + md.add(password); + md.calculate(); + + String result = md.toString(); + + if (!md5.equalsIgnoreCase(result)) + Serial.println("Odd - failing MD5 on String"); + else + Serial.println("OK!"); + } + // Check that this also work if we add the password not as + // a normal string - but as a string with the HEX values. + { + String passwordAsHex = "48656c6c6f20576f726c64"; + String md5 = "b10a8db164e0754105b7a99be72e3fe5"; + + MD5Builder md; + + md.begin(); + md.addHexString(passwordAsHex); + md.calculate(); + + String result = md.toString(); + + if (!md5.equalsIgnoreCase(result)) { + Serial.println("Odd - failing MD5 on hex string"); + Serial.println(md5); + Serial.println(result); + } + else + Serial.println("OK!"); + + } + // Check that this also work if we add the password as + // an unsigned byte array. + { + uint8_t password[] = { 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64 }; + String md5 = "b10a8db164e0754105b7a99be72e3fe5"; + MD5Builder md; + + md.begin(); + md.add(password, sizeof(password)); + md.calculate(); + + String result = md.toString(); + + if (!md5.equalsIgnoreCase(result)) + Serial.println("Odd - failing MD5 on byte array"); + else + Serial.println("OK!"); + + // And also check that we can compare this as pure, raw, bytes + // + uint8_t raw[16] = { 0xb1, 0x0a, 0x8d, 0xb1, 0x64, 0xe0, 0x75, 0x41, + 0x05, 0xb7, 0xa9, 0x9b, 0xe7, 0x2e, 0x3f, 0xe5 + }; + uint8_t res[16]; + md.getBytes(res); + if (memcmp(raw, res, 16)) + Serial.println("Odd - failing MD5 on byte array when compared as bytes"); + else + Serial.println("OK!"); + + } +} + +void loop() { +} \ No newline at end of file From 38b07785be5660042dcfe653575312e9d45123b1 Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Sat, 5 Nov 2022 20:34:57 +0100 Subject: [PATCH 08/21] Remove some duplicated code --- .../HttpBasicAuthSHA1orBearerToken.ino | 18 ++++-------------- libraries/WebServer/src/WebServer.cpp | 8 ++------ 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino b/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino index 1e2014f1ee4..7894f0e447f 100644 --- a/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino +++ b/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino @@ -80,21 +80,11 @@ void setup() { } ArduinoOTA.begin(); - // Convert token to a convenient binary representation. // - if (secret_token_hex.length() != 2 * 20) { - Serial.println("Bearer token does not look like a hex string ?!"); - } - - #define _H2D(x) (((x)>='0' && ((x) <='9')) ? ((x)-'0') : (((x)>='a' && (x)<='f') ? ((x)-'a'+10) : 0)) - #define H2D(x) (_H2D(tolower((x)))) - const char * _shaBase64 = secret_token_hex.c_str(); - for (int i = 0; i < 20; i++) { - unsigned char c = _shaBase64[2 * i + 0]; - unsigned char d = _shaBase64[2 * i + 1]; - _bearer[i] = (H2D(c) << 4) | H2D(d); - }; + size_t len = HEXBuilder::hex2bytes(_bearer,sizeof(_bearer),secret_token_hex); + if (len != 20) + Serial.println("Bearer token does not look like a valid SHA1 hex string ?!"); server.on("/", []() { if (!server.authenticate(&check_bearer_or_auth)) { @@ -116,4 +106,4 @@ void loop() { ArduinoOTA.handle(); server.handleClient(); delay(2);//allow the cpu to switch to other tasks -} +} \ No newline at end of file diff --git a/libraries/WebServer/src/WebServer.cpp b/libraries/WebServer/src/WebServer.cpp index 57ad47360b5..1087313f811 100644 --- a/libraries/WebServer/src/WebServer.cpp +++ b/libraries/WebServer/src/WebServer.cpp @@ -28,6 +28,7 @@ #include "WiFiServer.h" #include "WiFiClient.h" #include "WebServer.h" +#include "HEXBuilder.h" #include "FS.h" #include "detail/RequestHandlersImpl.h" #include "mbedtls/md5.h" @@ -164,12 +165,7 @@ bool WebServer::authenticateBasicSHA1(const char * _username, const char * _sha1 #define _H2D(x) (((x)>='0' && ((x) <='9')) ? ((x)-'0') : (((x)>='a' && (x)<='f') ? ((x)-'a'+10) : 0)) #define H2D(x) (_H2D(tolower((x)))) if (strlen(_sha1Base64orHex) == 20 * 2) { - for(int i = 0; i < 20; i++) { - unsigned char c = _sha1Base64orHex[2*i]; - unsigned char d = _sha1Base64orHex[2*i+1]; - sha1calc[i] = (H2D(c)<<4) | H2D(d); - }; - ret = 0; + HEXBuilder::bytes2hex(sha1calc,sizeof(sha1calc),sha1,sizeof(sha1)); } else ret = mbedtls_base64_encode((uint8_t*)sha1calc, sizeof(sha1calc), &olen, sha1, sizeof(sha1)); From 6ca0c7dfc400b4c6b7e3690001d01dc382a8fe16 Mon Sep 17 00:00:00 2001 From: Dirk-Willem van Gulik Date: Sat, 5 Nov 2022 20:53:21 +0100 Subject: [PATCH 09/21] Add simplfiied HEXBuilder under MD5Bulder to CMakefile. --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e065f90605..5f938b2dbce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,7 @@ set(CORE_SRCS cores/esp32/libb64/cencode.c cores/esp32/main.cpp cores/esp32/MD5Builder.cpp + cores/esp32/HEXBuilder.cpp cores/esp32/Print.cpp cores/esp32/stdlib_noniso.c cores/esp32/Stream.cpp From dcd64e138809eb097c2cfcd57cc81ab897c811cf Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Mon, 8 Jan 2024 13:35:51 -0300 Subject: [PATCH 10/21] Update for 3.0.0 and QoL improvements --- cores/esp32/HEXBuilder.h | 4 +- .../ESP32/examples/HEXBuilder/HEXBuilder.ino | 5 +- .../ESP32/examples/MD5Builder/MD5Builder.ino | 9 +- .../HttpAuthCallback/HttpAuthCallback.ino | 3 +- .../HttpAuthCallbackInline.ino | 3 +- .../HttpAuthOneTimePassword.ino | 9 +- .../HttpAuthOneTimePasswordNaive.ino | 3 +- .../HttpBasicAuthSHA1/HttpBasicAuthSHA1.ino | 3 +- .../HttpBasicAuthSHA1orBearerToken.ino | 7 +- libraries/WebServer/src/WebServer.cpp | 245 +++++++++++------- libraries/WebServer/src/WebServer.h | 4 +- 11 files changed, 182 insertions(+), 113 deletions(-) diff --git a/cores/esp32/HEXBuilder.h b/cores/esp32/HEXBuilder.h index 0bc2353ec45..1ff1b32c0fd 100644 --- a/cores/esp32/HEXBuilder.h +++ b/cores/esp32/HEXBuilder.h @@ -16,8 +16,8 @@ License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef __ESP8266_HEX_BUILDER__ -#define __ESP8266_HEX_BUILDER__ +#ifndef HEXBuilder_h +#define HEXBuilder_h #include #include diff --git a/libraries/ESP32/examples/HEXBuilder/HEXBuilder.ino b/libraries/ESP32/examples/HEXBuilder/HEXBuilder.ino index ad59976af58..7e5a18302e9 100644 --- a/libraries/ESP32/examples/HEXBuilder/HEXBuilder.ino +++ b/libraries/ESP32/examples/HEXBuilder/HEXBuilder.ino @@ -2,7 +2,7 @@ void setup() { Serial.begin(115200); - delay(100); + while (!Serial) { delay(10); } Serial.println("\n\n\nStart."); // Convert a HEX string like 6c6c6f20576f726c64 to a binary buffer @@ -72,5 +72,4 @@ void setup() { Serial.println("Done."); } -void loop() { -} +void loop() {} diff --git a/libraries/ESP32/examples/MD5Builder/MD5Builder.ino b/libraries/ESP32/examples/MD5Builder/MD5Builder.ino index d6842e84b60..0c28195049c 100644 --- a/libraries/ESP32/examples/MD5Builder/MD5Builder.ino +++ b/libraries/ESP32/examples/MD5Builder/MD5Builder.ino @@ -12,7 +12,7 @@ // void setup() { Serial.begin(115200); - delay(100); + while (!Serial) { delay(10); } Serial.println("\n\n\nStart."); // Check if a password obfuscated in an MD5 actually @@ -85,13 +85,12 @@ void setup() { }; uint8_t res[16]; md.getBytes(res); - if (memcmp(raw, res, 16)) - Serial.println("Odd - failing MD5 on byte array when compared as bytes"); + if (memcmp(raw, res, 16)) + Serial.println("Odd - failing MD5 on byte array when compared as bytes"); else Serial.println("OK!"); } } -void loop() { -} \ No newline at end of file +void loop() {} diff --git a/libraries/WebServer/examples/HttpAuthCallback/HttpAuthCallback.ino b/libraries/WebServer/examples/HttpAuthCallback/HttpAuthCallback.ino index 1fc99d826ae..8cdded55a1c 100644 --- a/libraries/WebServer/examples/HttpAuthCallback/HttpAuthCallback.ino +++ b/libraries/WebServer/examples/HttpAuthCallback/HttpAuthCallback.ino @@ -33,6 +33,7 @@ String * credentialsHandler(HTTPAuthMethod mode, String username, String params[ void setup() { Serial.begin(115200); + while (!Serial) { delay(10); } WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { @@ -60,4 +61,4 @@ void loop() { ArduinoOTA.handle(); server.handleClient(); delay(2);//allow the cpu to switch to other tasks -} \ No newline at end of file +} diff --git a/libraries/WebServer/examples/HttpAuthCallbackInline/HttpAuthCallbackInline.ino b/libraries/WebServer/examples/HttpAuthCallbackInline/HttpAuthCallbackInline.ino index 990207a0aea..48f0d3f8127 100644 --- a/libraries/WebServer/examples/HttpAuthCallbackInline/HttpAuthCallbackInline.ino +++ b/libraries/WebServer/examples/HttpAuthCallbackInline/HttpAuthCallbackInline.ino @@ -24,6 +24,7 @@ credentials_t passwdfile[] = { void setup() { Serial.begin(115200); + while (!Serial) { delay(10); } WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { @@ -63,4 +64,4 @@ void loop() { ArduinoOTA.handle(); server.handleClient(); delay(2);//allow the cpu to switch to other tasks -} \ No newline at end of file +} diff --git a/libraries/WebServer/examples/HttpAuthOneTimePassword/HttpAuthOneTimePassword.ino b/libraries/WebServer/examples/HttpAuthOneTimePassword/HttpAuthOneTimePassword.ino index 0cb03cc2825..1238ba44740 100644 --- a/libraries/WebServer/examples/HttpAuthOneTimePassword/HttpAuthOneTimePassword.ino +++ b/libraries/WebServer/examples/HttpAuthOneTimePassword/HttpAuthOneTimePassword.ino @@ -1,8 +1,3 @@ -#if 1 -// bodge to get this example to compile in the default install. -void setup() {}; -void loop() {}; -#else #include #include #include @@ -135,7 +130,7 @@ String * checkOTP(HTTPAuthMethod mode, String username, String params[]) { // for (int i = -2 ; i <= 2; i++) { String * otp = TOTP::currentOTP(time(NULL) + i * TOTP::RFC6238_DEFAULT_interval, *seed); - + if (!params[0].startsWith(*otp)) continue; @@ -159,6 +154,7 @@ String * checkOTP(HTTPAuthMethod mode, String username, String params[]) { void setup() { Serial.begin(115200); + while (!Serial) { delay(10); } WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { @@ -224,4 +220,3 @@ void loop() { delete otp; } } -#endif diff --git a/libraries/WebServer/examples/HttpAuthOneTimePasswordNaive/HttpAuthOneTimePasswordNaive.ino b/libraries/WebServer/examples/HttpAuthOneTimePasswordNaive/HttpAuthOneTimePasswordNaive.ino index efe2975119f..6a41ed23861 100644 --- a/libraries/WebServer/examples/HttpAuthOneTimePasswordNaive/HttpAuthOneTimePasswordNaive.ino +++ b/libraries/WebServer/examples/HttpAuthOneTimePasswordNaive/HttpAuthOneTimePasswordNaive.ino @@ -77,6 +77,7 @@ const int digits = 6; // length (default is 0) void setup() { Serial.begin(115200); + while (!Serial) { delay(10); } WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { @@ -92,7 +93,7 @@ void setup() { server.on("/", []() { if (!server.authenticate([](HTTPAuthMethod mode, String username, String params[]) -> String * { - if (username == demouser) + if (username == demouser) return TOTP::currentOTP(seed); return NULL; })) diff --git a/libraries/WebServer/examples/HttpBasicAuthSHA1/HttpBasicAuthSHA1.ino b/libraries/WebServer/examples/HttpBasicAuthSHA1/HttpBasicAuthSHA1.ino index fe8a51e375c..53750c1a414 100644 --- a/libraries/WebServer/examples/HttpBasicAuthSHA1/HttpBasicAuthSHA1.ino +++ b/libraries/WebServer/examples/HttpBasicAuthSHA1/HttpBasicAuthSHA1.ino @@ -35,6 +35,7 @@ const char* www_password_base64 = "jLEk+MJ3wW7Asu4AVp/RUaCONCs="; void setup() { Serial.begin(115200); + while (!Serial) { delay(10); } WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { @@ -72,4 +73,4 @@ void loop() { ArduinoOTA.handle(); server.handleClient(); delay(2);//allow the cpu to switch to other tasks -} \ No newline at end of file +} diff --git a/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino b/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino index 7894f0e447f..35ae96e71a5 100644 --- a/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino +++ b/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino @@ -51,7 +51,7 @@ String * check_bearer_or_auth(HTTPAuthMethod mode, String authReq, String params // we expect authReq to be "bearer some-secret" // String lcAuthReq = authReq; - lcAuthReq.toLowerCase(); + lcAuthReq.toLowerCase(); if (mode == OTHER_AUTH && (lcAuthReq.startsWith("bearer "))) { String secret = authReq.substring(7); secret.trim(); @@ -71,6 +71,7 @@ return server.authenticateBasicSHA1(www_username_hex, www_password_hex) ? void setup() { Serial.begin(115200); + while (!Serial) { delay(10); } WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { @@ -91,7 +92,7 @@ void setup() { Serial.println("No/failed authentication"); return server.requestAuthentication(); } - server.send(200, "text/plain", "Login okOK"); + server.send(200, "text/plain", "Login OK"); return; }); @@ -106,4 +107,4 @@ void loop() { ArduinoOTA.handle(); server.handleClient(); delay(2);//allow the cpu to switch to other tasks -} \ No newline at end of file +} diff --git a/libraries/WebServer/src/WebServer.cpp b/libraries/WebServer/src/WebServer.cpp index ea9b4d5692e..889e93c23ba 100644 --- a/libraries/WebServer/src/WebServer.cpp +++ b/libraries/WebServer/src/WebServer.cpp @@ -23,15 +23,18 @@ #include #include +#include #include #include "esp_random.h" #include "WiFiServer.h" #include "WiFiClient.h" #include "WebServer.h" +#include "HEXBuilder.h" #include "FS.h" #include "detail/RequestHandlersImpl.h" #include "MD5Builder.h" - +#include "mbedtls/sha1.h" +#include "mbedtls/base64.h" static const char AUTHORIZATION_HEADER[] = "Authorization"; static const char qop_auth[] PROGMEM = "qop=auth"; @@ -128,91 +131,159 @@ static String md5str(String &in){ return md5.toString(); } -bool WebServer::authenticate(const char * username, const char * password){ - if(hasHeader(FPSTR(AUTHORIZATION_HEADER))) { - String authReq = header(FPSTR(AUTHORIZATION_HEADER)); - if(authReq.startsWith(F("Basic"))){ - authReq = authReq.substring(6); - authReq.trim(); - char toencodeLen = strlen(username)+strlen(password)+1; - char *toencode = (char *)malloc(toencodeLen + 1); - if(toencode == NULL){ - authReq = ""; - return false; - } - char *encoded = (char *)malloc(base64_encode_expected_len(toencodeLen)+1); - if(encoded == NULL){ - authReq = ""; - free(toencode); - return false; - } - sprintf(toencode, "%s:%s", username, password); - if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equalsConstantTime(encoded)) { - authReq = ""; - free(toencode); - free(encoded); - return true; - } - free(toencode); - free(encoded);; - } else if(authReq.startsWith(F("Digest"))) { - authReq = authReq.substring(7); - log_v("%s", authReq.c_str()); - String _username = _extractParam(authReq,F("username=\""),'\"'); - if(!_username.length() || _username != String(username)) { - authReq = ""; - return false; - } - // extracting required parameters for RFC 2069 simpler Digest - String _realm = _extractParam(authReq, F("realm=\""),'\"'); - String _nonce = _extractParam(authReq, F("nonce=\""),'\"'); - String _uri = _extractParam(authReq, F("uri=\""),'\"'); - String _response = _extractParam(authReq, F("response=\""),'\"'); - String _opaque = _extractParam(authReq, F("opaque=\""),'\"'); - - if((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_response.length()) || (!_opaque.length())) { - authReq = ""; - return false; - } - if((_opaque != _sopaque) || (_nonce != _snonce) || (_realm != _srealm)) { - authReq = ""; - return false; - } - // parameters for the RFC 2617 newer Digest - String _nc,_cnonce; - if(authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) { - _nc = _extractParam(authReq, F("nc="), ','); - _cnonce = _extractParam(authReq, F("cnonce=\""),'\"'); - } - String _H1 = md5str(String(username) + ':' + _realm + ':' + String(password)); - log_v("Hash of user:realm:pass=%s", _H1.c_str()); - String _H2 = ""; - if(_currentMethod == HTTP_GET){ - _H2 = md5str(String(F("GET:")) + _uri); - }else if(_currentMethod == HTTP_POST){ - _H2 = md5str(String(F("POST:")) + _uri); - }else if(_currentMethod == HTTP_PUT){ - _H2 = md5str(String(F("PUT:")) + _uri); - }else if(_currentMethod == HTTP_DELETE){ - _H2 = md5str(String(F("DELETE:")) + _uri); - }else{ - _H2 = md5str(String(F("GET:")) + _uri); - } - log_v("Hash of GET:uri=%s", _H2.c_str()); - String _responsecheck = ""; - if(authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) { - _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2); - } else { - _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2); - } - log_v("The Proper response=%s", _responsecheck.c_str()); - if(_response == _responsecheck){ - authReq = ""; - return true; - } +bool WebServer::authenticateBasicSHA1(const char * _username, const char * _sha1Base64orHex) { + return WebServer::authenticate([_username,_sha1Base64orHex](HTTPAuthMethod mode, String username, String params[]) -> String * { + // rather than work on a password to compare with; we take the sha1 of the + // password received over the wire and compare that to the base64 encoded + // sha1 passed as _sha1base64. That way there is no need to have a + // plaintext password in the code/binary (though note that SHA1 is well + // past its retirement age). When that matches - we `cheat' by returning + // the password we got in the first place; so the normal BasicAuth + // can be completed. Note that this cannot work for a digest auth - + // as there the password in the clear is part of the calculation. + uint8_t sha1[20]; + char sha1calc[48]; // large enough for base64 and Hex represenation + size_t olen = 0; + int ret = 0; + + mbedtls_sha1((const uint8_t*) params[0].c_str(),params[0].length(),sha1); + + // we can either decode _sha1base64orHex and then compare the 20 bytes; + // or encode the sha we calculated. We pick the latter as encoding of a + // fixed array of 20 bytes s safer than operating on something external. + // + #define _H2D(x) (((x)>='0' && ((x) <='9')) ? ((x)-'0') : (((x)>='a' && (x)<='f') ? ((x)-'a'+10) : 0)) + #define H2D(x) (_H2D(tolower((x)))) + if (strlen(_sha1Base64orHex) == 20 * 2) { + HEXBuilder::bytes2hex(sha1calc,sizeof(sha1calc),sha1,sizeof(sha1)); + } else { + ret = mbedtls_base64_encode((uint8_t*)sha1calc, sizeof(sha1calc), &olen, sha1, sizeof(sha1)); + } + + return ((username.equalsConstantTime(_username)) && + (String((char*)sha1calc).equalsConstantTime(_sha1Base64orHex)) && + (ret == 0) && + (mode == BASIC_AUTH) /* to keep things somewhat time constant. */ + ) ? new String(params[0]) : NULL; + }); +} + +bool WebServer::authenticate(const char * _username, const char * _password){ + return WebServer::authenticate([_username,_password](HTTPAuthMethod mode, String username, String params[]) -> String * { + return username.equalsConstantTime(_username) ? new String(_password) : NULL; + }); +} + +bool WebServer::authenticate(THandlerFunctionAuthCheck fn) { + if(!hasHeader(FPSTR(AUTHORIZATION_HEADER))) + return false; + + String authReq = header(FPSTR(AUTHORIZATION_HEADER)); + if(authReq.startsWith(AuthTypeBasic)) { + bool ret = false; + + authReq = authReq.substring(6); // length of AuthTypeBasic including the space at the end. + authReq.trim(); + + /* base64 encoded string is always shorter (or equal) in length */ + char *decoded = (authReq.length() < HTTP_MAX_BASIC_AUTH_LEN) ? new char[authReq.length()] : NULL; + if (decoded) { + char * p; + if (base64_decode_chars(authReq.c_str(), authReq.length(), decoded) && (p = index(decoded,':')) && p) { + authReq = ""; + /* Note: rfc7617 guarantees that there will not be an escaped colon in the username itself. + * Note: base64_decode_chars() guarantees a terminating \0 + */ + *p = '\0'; + char * _username = decoded, *_password = p+1; + String params[] = { _password, _srealm }; + String * password = fn(BASIC_AUTH, _username, params); + if (password) { + ret = password->equalsConstantTime(_password); + // we're more concerned about the password; as the attacker already + // knows the _pasword. Arduino's string handling is simple; it reallocs + // even when smaller; so a memset is enough (no capacity/size). + memset((void*)password->c_str(),0,password->length()); + delete password; + }; + }; + delete[] decoded; + }; + authReq = ""; + return ret; + } else if(authReq.startsWith(AuthTypeDigest)) { + authReq = authReq.substring(7); + log_v("%s", authReq.c_str()); + + // extracting required parameters for RFC 2069 simpler Digest + // + String _username = _extractParam(authReq,F("username=\""),'\"'); + String _realm = _extractParam(authReq, F("realm=\""),'\"'); + String _uri = _extractParam(authReq, F("uri=\""),'\"'); + if (!_username.length()) + goto exf; + + String params[] = { _realm, _uri }; + String * password = fn(DIGEST_AUTH,_username, params); + if(!password) + goto exf; + + String _H1 = md5str(String(_username) + ':' + _realm + ':' + *password); + // we're extra concerned; as digest request us to know the password + // in the clear. + memset((void*)password->c_str(),0,password->length()); + delete password; + _username = ""; + + String _nonce = _extractParam(authReq, F("nonce=\""),'\"'); + String _response = _extractParam(authReq, F("response=\""),'\"'); + String _opaque = _extractParam(authReq, F("opaque=\""),'\"'); + + if((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_response.length()) || (!_opaque.length())) + goto exf; + + if((_opaque != _sopaque) || (_nonce != _snonce) || (_realm != _srealm)) + goto exf; + + // parameters for the RFC 2617 newer Digest + // + String _nc,_cnonce; + if(authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) { + _nc = _extractParam(authReq, F("nc="), ','); + _cnonce = _extractParam(authReq, F("cnonce=\""),'\"'); + } + + log_v("Hash of user:realm:pass=%s", _H1.c_str()); + String _H2 = ""; + if(_currentMethod == HTTP_GET){ + _H2 = md5str(String(F("GET:")) + _uri); + }else if(_currentMethod == HTTP_POST){ + _H2 = md5str(String(F("POST:")) + _uri); + }else if(_currentMethod == HTTP_PUT){ + _H2 = md5str(String(F("PUT:")) + _uri); + }else if(_currentMethod == HTTP_DELETE){ + _H2 = md5str(String(F("DELETE:")) + _uri); + }else{ + _H2 = md5str(String(F("GET:")) + _uri); + } + log_v("Hash of GET:uri=%s", _H2.c_str()); + String _responsecheck = ""; + if(authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) { + _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2); + } else { + _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2); } authReq = ""; + + log_v("The Proper response=%s", _responsecheck.c_str()); + return _response == _responsecheck; + } else if (authReq.length()) { + String * ret = fn(OTHER_AUTH, authReq, {}); + if (ret) + return true; } +exf: + authReq = ""; return false; } @@ -232,11 +303,11 @@ void WebServer::requestAuthentication(HTTPAuthMethod mode, const char* realm, co _srealm = String(realm); } if(mode == BASIC_AUTH) { - sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Basic realm=\"")) + _srealm + String(F("\""))); + sendHeader(String(FPSTR(WWW_Authenticate)), AuthTypeBasic + String(F(" realm=\"")) + _srealm + String(F("\""))); } else { _snonce=_getRandomHexString(); _sopaque=_getRandomHexString(); - sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Digest realm=\"")) +_srealm + String(F("\", qop=\"auth\", nonce=\"")) + _snonce + String(F("\", opaque=\"")) + _sopaque + String(F("\""))); + sendHeader(String(FPSTR(WWW_Authenticate)), AuthTypeDigest + String(F(" realm=\"")) +_srealm + String(F("\", qop=\"auth\", nonce=\"")) + _snonce + String(F("\", opaque=\"")) + _sopaque + String(F("\""))); } using namespace mime; send(401, String(FPSTR(mimeTable[html].mimeType)), authFailMsg); @@ -411,8 +482,8 @@ void WebServer::_prepareHeader(String& response, int code, const char* content_t } if (_corsEnabled) { sendHeader(String(FPSTR("Access-Control-Allow-Origin")), String("*")); - sendHeader(String(FPSTR("Access-Control-Allow-Methods")), String("*")); - sendHeader(String(FPSTR("Access-Control-Allow-Headers")), String("*")); + sendHeader(String(FPSTR("Access-Control-Allow-Methods")), String("*")); + sendHeader(String(FPSTR("Access-Control-Allow-Headers")), String("*")); } sendHeader(String(F("Connection")), String(F("close"))); diff --git a/libraries/WebServer/src/WebServer.h b/libraries/WebServer/src/WebServer.h index 7d3caecf08a..95f36aac9d1 100644 --- a/libraries/WebServer/src/WebServer.h +++ b/libraries/WebServer/src/WebServer.h @@ -100,7 +100,7 @@ class WebServer * param[1] authentication URI * * To return - the password of which the digest will be based on for comparison. Or NULL - * to fail. + * to fail. * * OTHER_AUTH enteredUsernameOrReq rest of the auth line. * params empty array @@ -117,7 +117,7 @@ class WebServer typedef std::function THandlerFunction; void on(const Uri &uri, THandlerFunction fn); - void on(const Uri &uri, HTTPMethod method, THandlerFunction fn); + void on(const Uri &uri, HTTPMethod method, THandlerFunction fn); void on(const Uri &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn); //ufn handles file uploads void addHandler(RequestHandler* handler); void serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_header = NULL ); From 68136725fd13a92548d35587cb1a78abd1b73801 Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Mon, 8 Jan 2024 15:41:48 -0300 Subject: [PATCH 11/21] Remove examples that depend on external libraries --- .../HttpAuthOneTimePassword.ino | 222 ------------------ .../HttpAuthOneTimePasswordNaive.ino | 138 ----------- 2 files changed, 360 deletions(-) delete mode 100644 libraries/WebServer/examples/HttpAuthOneTimePassword/HttpAuthOneTimePassword.ino delete mode 100644 libraries/WebServer/examples/HttpAuthOneTimePasswordNaive/HttpAuthOneTimePasswordNaive.ino diff --git a/libraries/WebServer/examples/HttpAuthOneTimePassword/HttpAuthOneTimePassword.ino b/libraries/WebServer/examples/HttpAuthOneTimePassword/HttpAuthOneTimePassword.ino deleted file mode 100644 index 1238ba44740..00000000000 --- a/libraries/WebServer/examples/HttpAuthOneTimePassword/HttpAuthOneTimePassword.ino +++ /dev/null @@ -1,222 +0,0 @@ -#include -#include -#include -#include -#include - -// https://github.com/dirkx/Arduino-Base32-Decode or simply from -// Sketch->Include Library->Library manager and search for -// "Base32-Decode". -// -#include - -// https://github.com/dirkx/Arduino-TOTP-RFC6238-generator or simply from -// Sketch->Include Library->Library manager and search for -// "TOTP-RC6236-generator". -// -#include - -#include // for the hmac/ cookie - -#ifndef WIFI_NETWORK -#define WIFI_NETWORK "mySecretWiFiPassword" -#warning "You propably want to change this line!" -#endif - -#ifndef WIFI_PASSWD -#define WIFI_PASSWD "mySecretWiFiPassword" -#warning "You propably want to change this line!" -#endif - -#ifndef NTP_SERVER -#define NTP_SERVER "nl.pool.ntp.org" -#warning "You MUST set an appropriate ntp pool - see http://ntp.org" -#endif - -#ifndef NTP_DEFAULT_TZ -#define NTP_DEFAULT_TZ "CET-1CEST,M3.5.0,M10.5.0/3" -#endif - - -const char* ssid = WIFI_NETWORK; -const char* password = WIFI_PASSWD; - -WebServer server(80); - -String demousers[] = { "admin", "user1", "user2" }; -String demopasswords[] = { "secret", "secr!t", "s3cret" }; -String demoseeds[] = { "ORUGKU3FMNZGK5CTMVSWI", "ORUGKU3FMNZGK5CTMVSWIMQ", "ORUGKU3FMNZGK5CTMVSWIMY"}; - -const unsigned long cookie_secret_seed[2] = { esp_random(), esp_random() }; - -const int LOGIN_TIMEOUT = 30; // 30 minute timeout. -#define LOGIN_COOKIE_NAME "token" - -String hmac(time_t t) { - unsigned char digest[32]; - char digest_hex[64 + 1]; - if (mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), - (unsigned char *) & (cookie_secret_seed[0]), sizeof(cookie_secret_seed), // key - (unsigned char *) & t, sizeof(t), // input - digest)) - return ""; // safe - as we compare it with something known to be 64 hex bytes. - - for (int i = 0; i < sizeof(digest); i++) - sprintf(digest_hex + i * 2, "%02X", digest[i]); - - return String(digest_hex); -} - -// Issue 1) Once the user has logged in once; with a valid Time based OTP; that OTP will -// only be valid for up to LOGIN_TIMEOUT seconds. Then it won't work anymore. -// We solve this by setting a secure cookie that is valid for an hour. -// And if we see that - we assume all is well. -// -bool hasValidCookie(WebServer &server) { - if (!server.hasHeader("Cookie")) - return false; - - String cookie = server.header("Cookie"); - int at = cookie.indexOf(LOGIN_COOKIE_NAME "="); - if (at < 0) - return false; - - String token = cookie.substring(at + 7, 16 + 64); - if (token.length() != 16 + 64) - return false; - String time_str = token.substring(0, 16); - String hmac_str = token.substring(16, 64); - - time_t issued = strtoul(time_str.c_str(), NULL, 10); - time_t now = time(NULL); - if (now - issued > LOGIN_TIMEOUT * 60) - return false; - - return hmac(issued).equals(hmac_str); -} - -void setCookie(WebServer &server) { - char buff[16 + 64 + 1]; - snprintf(buff, sizeof(buff), "%016lu%s", time(NULL), hmac(time(NULL)).c_str()); - server.sendHeader("Set-Cookie", LOGIN_COOKIE_NAME "=" + String(buff)); -} - -String * checkOTP(HTTPAuthMethod mode, String username, String params[]) { - if (mode != BASIC_AUTH) { - Serial.println("Not basic auth - rejecting"); - return NULL; - }; - - // find the password and seed for this user. - // - String * passwd = NULL, * seed = NULL; - for (int i = 0; i < sizeof(demousers) / sizeof(String); i++) { - if (demousers[i] == username) { - passwd = &demopasswords[i]; - seed = &demoseeds[i]; - break; - } - }; - - if (!passwd || !seed) { - Serial.println("Unknown username - rejecting"); - return NULL; - }; - - // Issue 2) We anticipate some clock skew - so try 30 seconds 'around' the current time'; - // and if we're more clever; we could track which people use; and then adjust our - // idea of how much our clock is likely behind or ahead; along with that of - // people their typical clocks. - // - for (int i = -2 ; i <= 2; i++) { - String * otp = TOTP::currentOTP(time(NULL) + i * TOTP::RFC6238_DEFAULT_interval, *seed); - - if (!params[0].startsWith(*otp)) - continue; - - // Issue 3) Use proper 2FA/MFA - and insist the user types the token followed by the password - // - if (!params[0].substring(6).equals(*passwd)) { - Serial.println("Incorrect password - rejecting"); - return NULL; - } - - // entirely `bypass' the authentication system; and return - // the password as entered; as that is known to be 'ok' - // - Serial.print("User "); Serial.print(username); Serial.println(" logged in"); - return new String(params[0]); - } // clockskew loop - - Serial.println("All OTPs wrong - rejecting"); - return NULL; -} - -void setup() { - Serial.begin(115200); - while (!Serial) { delay(10); } - 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(); - - // we need a reasonable accurate time for TOTP to work. - // - configTzTime(NTP_DEFAULT_TZ, NTP_SERVER); - - server.on("/", []() { - if (!hasValidCookie(server) && !server.authenticate(&checkOTP)) { - server.requestAuthentication(); - return; - } - // set or renew the cookie. - setCookie(server); - - // Let the user in. - 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(); - static unsigned long last = millis(); - if (millis() - last < 2000) - return; - last = millis(); - time_t t = time(NULL); - if (t < 1000000) { - Serial.println("Not having a stable time yet.. TOTP is not going to work."); - return; - }; - static time_t lst_i = 0; - time_t n = t / TOTP::RFC6238_DEFAULT_interval; - if (n == lst_i) - return; - lst_i = n; - - Serial.print(ctime(&t)); - for (int i = 0; i < sizeof(demousers) / sizeof(String); i++) { - String * otp = TOTP::currentOTP(demoseeds[i]); - - Serial.print(" "); - Serial.print(demousers[i]); - Serial.print(" - "); - Serial.print(*otp); - Serial.print(demopasswords[i]); - Serial.print(" - (Seed for calculator: "); - Serial.print(demoseeds[i]); - Serial.println(")"); - - delete otp; - } -} diff --git a/libraries/WebServer/examples/HttpAuthOneTimePasswordNaive/HttpAuthOneTimePasswordNaive.ino b/libraries/WebServer/examples/HttpAuthOneTimePasswordNaive/HttpAuthOneTimePasswordNaive.ino deleted file mode 100644 index 6a41ed23861..00000000000 --- a/libraries/WebServer/examples/HttpAuthOneTimePasswordNaive/HttpAuthOneTimePasswordNaive.ino +++ /dev/null @@ -1,138 +0,0 @@ -#if 1 -// bodge to get this example to compile in the default install. -void setup() {}; -void loop() {}; -#else -#include -#include -#include -#include -#include - -// https://github.com/dirkx/Arduino-Base32-Decode or simply from -// Sketch->Include Library->Library manager and search for -// "Base32-Decode". -// -#include - -// https://github.com/dirkx/Arduino-TOTP-RFC6238-generator or simply from -// Sketch->Include Library->Library manager and search for -// "TOTP-RC6236-generator". -// -#include - -// Naive RFC 6238 one time password; with a couple of possibly flawed -// assumptionms: -// -// 1) we're going to assume that the clock is perfect! -// -// 2) we're going to assume you just need access for however long -// your OTP code is valid for. So that is at the most 30 seconds. -// -// 3) we're dispensing with a password; all you need to know is -// the username and the one time passcode of that moment. So this -// is not exactly MFA. -// -// Which is not very useful. But keeps this demo simple. -// - -#include // for the uint64_t typedef -#include // for the hmac/sha1 calculation - -#ifndef WIFI_NETWORK -#define WIFI_NETWORK "mySecretWiFiPassword" -#warning "You propably want to change this line!" -#endif - -#ifndef WIFI_PASSWD -#define WIFI_PASSWD "mySecretWiFiPassword" -#warning "You propably want to change this line!" -#endif - -#ifndef NTP_SERVER -#define NTP_SERVER "nl.pool.ntp.org" -#warning "You MUST set an appropriate ntp pool - see http://ntp.org" -#endif - -#ifndef NTP_DEFAULT_TZ -#define NTP_DEFAULT_TZ "CET-1CEST,M3.5.0,M10.5.0/3" -#endif - - -const char* ssid = WIFI_NETWORK; -const char* password = WIFI_PASSWD; - -WebServer server(80); - -const String demouser = "admin"; - -// we only define one seed, for the user admin. Normally every user would -// have their own seed and also enter a password - to make this proper -// two factor authentication (see note 1 below) -// -const String seed = "theSecretSeed"; // must be a Base32 string; can be == padded. -const time_t interval = 30; // seconds (default) -const time_t epoch = 0; // epoch relative to the unix epoch (jan 1970 is the default) -const int digits = 6; // length (default is 0) - -void setup() { - Serial.begin(115200); - while (!Serial) { delay(10); } - 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(); - - // we need a reasonable accurate time for TOTP to work. - // - configTzTime(NTP_DEFAULT_TZ, NTP_SERVER); - - server.on("/", []() { - if (!server.authenticate([](HTTPAuthMethod mode, String username, String params[]) -> String * { - if (username == demouser) - return TOTP::currentOTP(seed); - return NULL; - })) - { - server.requestAuthentication(); - return; - } - 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(); - static unsigned long last = millis(); - if (millis() - last < 2000) - return; - last = millis(); - time_t t = time(NULL); - if (t < 1000000) { - Serial.println("Not having a stable time yet.. TOTP is not going to work."); - return; - }; - static time_t lst_i = 0; - time_t n = t / interval; - if (n == lst_i) - return; - lst_i = n; - - Serial.print(ctime(&t)); - Serial.print(" TOTP at this time is: "); - String * otp = TOTP::currentOTP(seed); - Serial.println(*otp); - Serial.println(); - delete otp; -} -#endif From c3ff7e047a74a3e2fb99eb4cfb82bbb9c5e9e6a9 Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Tue, 9 Jan 2024 08:53:05 -0300 Subject: [PATCH 12/21] Skip H2 testing --- libraries/WebServer/examples/HttpAuthCallback/.skip.esp32h2 | 0 libraries/WebServer/examples/HttpAuthCallbackInline/.skip.esp32h2 | 0 libraries/WebServer/examples/HttpBasicAuthSHA1/.skip.esp32h2 | 0 .../examples/HttpBasicAuthSHA1orBearerToken/.skip.esp32h2 | 0 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 libraries/WebServer/examples/HttpAuthCallback/.skip.esp32h2 create mode 100644 libraries/WebServer/examples/HttpAuthCallbackInline/.skip.esp32h2 create mode 100644 libraries/WebServer/examples/HttpBasicAuthSHA1/.skip.esp32h2 create mode 100644 libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/.skip.esp32h2 diff --git a/libraries/WebServer/examples/HttpAuthCallback/.skip.esp32h2 b/libraries/WebServer/examples/HttpAuthCallback/.skip.esp32h2 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/WebServer/examples/HttpAuthCallbackInline/.skip.esp32h2 b/libraries/WebServer/examples/HttpAuthCallbackInline/.skip.esp32h2 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/WebServer/examples/HttpBasicAuthSHA1/.skip.esp32h2 b/libraries/WebServer/examples/HttpBasicAuthSHA1/.skip.esp32h2 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/.skip.esp32h2 b/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/.skip.esp32h2 new file mode 100644 index 00000000000..e69de29bb2d From d24668efab9ddb0ea8ffd83593e2ffdd32998d85 Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Tue, 9 Jan 2024 13:35:16 -0300 Subject: [PATCH 13/21] Formatting improvements --- libraries/ESP32/examples/HEXBuilder/HEXBuilder.ino | 4 ---- libraries/ESP32/examples/MD5Builder/MD5Builder.ino | 6 +++--- .../HttpBasicAuthSHA1/HttpBasicAuthSHA1.ino | 4 ---- .../HttpBasicAuthSHA1orBearerToken.ino | 14 ++++---------- libraries/WebServer/src/WebServer.cpp | 8 ++------ 5 files changed, 9 insertions(+), 27 deletions(-) diff --git a/libraries/ESP32/examples/HEXBuilder/HEXBuilder.ino b/libraries/ESP32/examples/HEXBuilder/HEXBuilder.ino index 7e5a18302e9..3e9ae5bfd53 100644 --- a/libraries/ESP32/examples/HEXBuilder/HEXBuilder.ino +++ b/libraries/ESP32/examples/HEXBuilder/HEXBuilder.ino @@ -6,7 +6,6 @@ void setup() { Serial.println("\n\n\nStart."); // Convert a HEX string like 6c6c6f20576f726c64 to a binary buffer - // { const char * out = "Hello World"; const char * hexin = "48656c6c6f20576f726c6400"; // As the string above is \0 terminated too @@ -21,7 +20,6 @@ void setup() { Serial.println("Odd - decode 1 went wrong"); // Safe to print this binary buffer -- as we've included a \0 in the hex sequence. - // Serial.printf("IN: <%s>\nOUT <%s\\0>\n", hexin, buff); }; @@ -49,7 +47,6 @@ void setup() { Serial.println("Odd - length 3 is wrong"); // we need to ignore case - as a hex string can be spelled in uppercase and lowercase - // if (!out.equalsIgnoreCase(helloHEX)) { Serial.println("Odd - decode 3 went wrong"); } @@ -65,7 +62,6 @@ void setup() { Serial.println("Odd - length 4 is wrong"); // we need to ignore case - as a hex string can be spelled in uppercase and lowercase - // if (strcasecmp(buff, helloHex)) Serial.println("Odd - decode 4 went wrong"); } diff --git a/libraries/ESP32/examples/MD5Builder/MD5Builder.ino b/libraries/ESP32/examples/MD5Builder/MD5Builder.ino index 0c28195049c..9b439587000 100644 --- a/libraries/ESP32/examples/MD5Builder/MD5Builder.ino +++ b/libraries/ESP32/examples/MD5Builder/MD5Builder.ino @@ -9,7 +9,7 @@ // has been phased out as insecure eons ago) by letting you create an // MD5 of the data the user entered; and then compare this to an MD5 // string that you have put in your code. -// + void setup() { Serial.begin(115200); while (!Serial) { delay(10); } @@ -19,7 +19,6 @@ void setup() { // matches the original string. // // echo -n "Hello World" | openssl md5 - // { String md5 = "b10a8db164e0754105b7a99be72e3fe5"; String password = "Hello World"; @@ -37,6 +36,7 @@ void setup() { else Serial.println("OK!"); } + // Check that this also work if we add the password not as // a normal string - but as a string with the HEX values. { @@ -60,6 +60,7 @@ void setup() { Serial.println("OK!"); } + // Check that this also work if we add the password as // an unsigned byte array. { @@ -79,7 +80,6 @@ void setup() { Serial.println("OK!"); // And also check that we can compare this as pure, raw, bytes - // uint8_t raw[16] = { 0xb1, 0x0a, 0x8d, 0xb1, 0x64, 0xe0, 0x75, 0x41, 0x05, 0xb7, 0xa9, 0x9b, 0xe7, 0x2e, 0x3f, 0xe5 }; diff --git a/libraries/WebServer/examples/HttpBasicAuthSHA1/HttpBasicAuthSHA1.ino b/libraries/WebServer/examples/HttpBasicAuthSHA1/HttpBasicAuthSHA1.ino index 53750c1a414..bfc2df6d037 100644 --- a/libraries/WebServer/examples/HttpBasicAuthSHA1/HttpBasicAuthSHA1.ino +++ b/libraries/WebServer/examples/HttpBasicAuthSHA1/HttpBasicAuthSHA1.ino @@ -15,24 +15,20 @@ WebServer server(80); // Passwords as plaintext - human readable and easily visible in // the sourcecode and in the firmware/binary. -// const char* www_username = "admin"; const char* www_password = "esp32"; // The sha1 of 'esp32' (without the trailing \0) expressed as 20 // bytes of hex. Created by for example 'echo -n esp32 | openssl sha1' // or http://www.sha1-online.com. -// const char* www_username_hex = "hexadmin"; const char* www_password_hex = "8cb124f8c277c16ec0b2ee00569fd151a08e342b"; // The same; but now expressed as a base64 string (e.g. as commonly used // by webservers). Created by ` echo -n esp32 | openssl sha1 -binary | openssl base64` -// const char* www_username_base64 = "base64admin"; const char* www_password_base64 = "jLEk+MJ3wW7Asu4AVp/RUaCONCs="; - void setup() { Serial.begin(115200); while (!Serial) { delay(10); } diff --git a/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino b/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino index 35ae96e71a5..9cec9f5c95c 100644 --- a/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino +++ b/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino @@ -4,8 +4,7 @@ #include #include "mbedtls/sha1.h" - -/// We have two options - we either come in with a bearer +// We have two options - we either come in with a bearer // token - i.e. a special header or API token; or we // get a normal HTTP style basic auth prompt. // @@ -18,11 +17,11 @@ // Create the secet token SHA1 with: // echo -n SecritToken | openssl sha1 -// + String secret_token_hex = "d2cce6b472959484a21c3194080c609b8a2c910b"; // Wifi credentials -// + const char* ssid = "........"; const char* password = "........"; @@ -36,20 +35,17 @@ WebServer server(80); // The sha1 of 'esp32' (without the trailing \0) expressed as 20 // bytes of hex. Created by for example 'echo -n esp32 | openssl sha1' // or http://www.sha1-online.com. -// const char* www_username_hex = "hexadmin"; const char* www_password_hex = "8cb124f8c277c16ec0b2ee00569fd151a08e342b"; // The same; but now expressed as a base64 string (e.g. as commonly used // by webservers). Created by ` echo -n esp32 | openssl sha1 -binary | openssl base64` -// const char* www_username_base64 = "base64admin"; const char* www_password_base64 = "jLEk+MJ3wW7Asu4AVp/RUaCONCs="; static unsigned char _bearer[20]; String * check_bearer_or_auth(HTTPAuthMethod mode, String authReq, String params[]) { // we expect authReq to be "bearer some-secret" - // String lcAuthReq = authReq; lcAuthReq.toLowerCase(); if (mode == OTHER_AUTH && (lcAuthReq.startsWith("bearer "))) { @@ -64,7 +60,6 @@ String * check_bearer_or_auth(HTTPAuthMethod mode, String authReq, String params }; // that failed - so do a normal auth -// return server.authenticateBasicSHA1(www_username_hex, www_password_hex) ? new String(params[0]) : NULL; }; @@ -82,9 +77,8 @@ void setup() { ArduinoOTA.begin(); // Convert token to a convenient binary representation. - // size_t len = HEXBuilder::hex2bytes(_bearer,sizeof(_bearer),secret_token_hex); - if (len != 20) + if (len != 20) Serial.println("Bearer token does not look like a valid SHA1 hex string ?!"); server.on("/", []() { diff --git a/libraries/WebServer/src/WebServer.cpp b/libraries/WebServer/src/WebServer.cpp index 889e93c23ba..780a753822b 100644 --- a/libraries/WebServer/src/WebServer.cpp +++ b/libraries/WebServer/src/WebServer.cpp @@ -43,7 +43,6 @@ static const char WWW_Authenticate[] = "WWW-Authenticate"; static const char Content_Length[] = "Content-Length"; static const char ETAG_HEADER[] = "If-None-Match"; - WebServer::WebServer(IPAddress addr, int port) : _corsEnabled(false) , _server(addr, port) @@ -151,7 +150,6 @@ bool WebServer::authenticateBasicSHA1(const char * _username, const char * _sha1 // we can either decode _sha1base64orHex and then compare the 20 bytes; // or encode the sha we calculated. We pick the latter as encoding of a // fixed array of 20 bytes s safer than operating on something external. - // #define _H2D(x) (((x)>='0' && ((x) <='9')) ? ((x)-'0') : (((x)>='a' && (x)<='f') ? ((x)-'a'+10) : 0)) #define H2D(x) (_H2D(tolower((x)))) if (strlen(_sha1Base64orHex) == 20 * 2) { @@ -216,7 +214,6 @@ bool WebServer::authenticate(THandlerFunctionAuthCheck fn) { log_v("%s", authReq.c_str()); // extracting required parameters for RFC 2069 simpler Digest - // String _username = _extractParam(authReq,F("username=\""),'\"'); String _realm = _extractParam(authReq, F("realm=\""),'\"'); String _uri = _extractParam(authReq, F("uri=\""),'\"'); @@ -239,14 +236,13 @@ bool WebServer::authenticate(THandlerFunctionAuthCheck fn) { String _response = _extractParam(authReq, F("response=\""),'\"'); String _opaque = _extractParam(authReq, F("opaque=\""),'\"'); - if((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_response.length()) || (!_opaque.length())) + if((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_response.length()) || (!_opaque.length())) goto exf; - if((_opaque != _sopaque) || (_nonce != _snonce) || (_realm != _srealm)) + if((_opaque != _sopaque) || (_nonce != _snonce) || (_realm != _srealm)) goto exf; // parameters for the RFC 2617 newer Digest - // String _nc,_cnonce; if(authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) { _nc = _extractParam(authReq, F("nc="), ','); From ffdd841886812dbfec1a5d4934c8e3d8be19a51d Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Thu, 11 Jan 2024 18:10:42 -0300 Subject: [PATCH 14/21] Move builders examples to Utilities folder --- .../ESP32/examples/{ => Utilities}/HEXBuilder/HEXBuilder.ino | 0 .../ESP32/examples/{ => Utilities}/MD5Builder/MD5Builder.ino | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename libraries/ESP32/examples/{ => Utilities}/HEXBuilder/HEXBuilder.ino (100%) rename libraries/ESP32/examples/{ => Utilities}/MD5Builder/MD5Builder.ino (100%) diff --git a/libraries/ESP32/examples/HEXBuilder/HEXBuilder.ino b/libraries/ESP32/examples/Utilities/HEXBuilder/HEXBuilder.ino similarity index 100% rename from libraries/ESP32/examples/HEXBuilder/HEXBuilder.ino rename to libraries/ESP32/examples/Utilities/HEXBuilder/HEXBuilder.ino diff --git a/libraries/ESP32/examples/MD5Builder/MD5Builder.ino b/libraries/ESP32/examples/Utilities/MD5Builder/MD5Builder.ino similarity index 100% rename from libraries/ESP32/examples/MD5Builder/MD5Builder.ino rename to libraries/ESP32/examples/Utilities/MD5Builder/MD5Builder.ino From dc1a920231369a6c46c347e17175661b35e60e0a Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Thu, 11 Jan 2024 18:11:39 -0300 Subject: [PATCH 15/21] Fix indentation --- cores/esp32/HEXBuilder.cpp | 66 ++++++++++++++++++++------------------ cores/esp32/HEXBuilder.h | 4 +-- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/cores/esp32/HEXBuilder.cpp b/cores/esp32/HEXBuilder.cpp index 7c456409798..f3b0f7a9b3e 100644 --- a/cores/esp32/HEXBuilder.cpp +++ b/cores/esp32/HEXBuilder.cpp @@ -1,7 +1,7 @@ -/* +/* Copyright (c) 2015 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - + This file is part of the esp32 core for Arduino environment. + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either @@ -16,6 +16,7 @@ License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + #include #include @@ -27,43 +28,44 @@ static uint8_t hex_char_to_byte(uint8_t c) } size_t HEXBuilder::hex2bytes(unsigned char * out, size_t maxlen, String &in) { - return hex2bytes(out, maxlen, in.c_str()); + return hex2bytes(out, maxlen, in.c_str()); } size_t HEXBuilder::hex2bytes(unsigned char * out, size_t maxlen, const char * in) { - size_t len = 0; - for(;*in;in++) { - uint8_t c = hex_char_to_byte(*in); - // Silently skip anything unknown. - if (c > 15) - continue; + size_t len = 0; + for(;*in;in++) { + uint8_t c = hex_char_to_byte(*in); + // Silently skip anything unknown. + if (c > 15) + continue; - if (len & 1) { - if (len/2 < maxlen) - out[len/2] |= c; - } else { - if (len/2 < maxlen) - out[len/2] = c<<4; - } - len++; - } - return (len + 1)/2; + if (len & 1) { + if (len/2 < maxlen) + out[len/2] |= c; + } else { + if (len/2 < maxlen) + out[len/2] = c<<4; + } + len++; + } + return (len + 1)/2; } size_t HEXBuilder::bytes2hex(char * out, size_t maxlen, const unsigned char * in, size_t len) { - for(size_t i = 0; i < len; i++) - if (i*2 + 1 < maxlen) - sprintf(out + (i * 2), "%02x", in[i]); - - return len * 2 + 1; + for(size_t i = 0; i < len; i++) { + if (i*2 + 1 < maxlen) { + sprintf(out + (i * 2), "%02x", in[i]); + } + } + return len * 2 + 1; } String HEXBuilder::bytes2hex(const unsigned char * in, size_t len) { - size_t maxlen = len * 2 + 1; - char * out = (char *) malloc(maxlen); - if (!out) return String(); - bytes2hex(out, maxlen, in, len); - String ret = String(out); - free(out); - return ret; + size_t maxlen = len * 2 + 1; + char * out = (char *) malloc(maxlen); + if (!out) return String(); + bytes2hex(out, maxlen, in, len); + String ret = String(out); + free(out); + return ret; } diff --git a/cores/esp32/HEXBuilder.h b/cores/esp32/HEXBuilder.h index 1ff1b32c0fd..d4d68e8c45d 100644 --- a/cores/esp32/HEXBuilder.h +++ b/cores/esp32/HEXBuilder.h @@ -1,7 +1,7 @@ -/* +/* Copyright (c) 2015 Hristo Gochkov. All rights reserved. This file is part of the esp8266 core for Arduino environment. - + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either From 289f59d593162e8d81ccce9808b04081a4822184 Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Thu, 11 Jan 2024 18:12:41 -0300 Subject: [PATCH 16/21] Add HashBuilder abstract class --- cores/esp32/HashBuilder.h | 60 ++++++++++++++++++++++++++++++++++++++ cores/esp32/MD5Builder.cpp | 5 ++-- cores/esp32/MD5Builder.h | 58 ++++++++++++++---------------------- 3 files changed, 85 insertions(+), 38 deletions(-) create mode 100644 cores/esp32/HashBuilder.h diff --git a/cores/esp32/HashBuilder.h b/cores/esp32/HashBuilder.h new file mode 100644 index 00000000000..ce6f1f1af42 --- /dev/null +++ b/cores/esp32/HashBuilder.h @@ -0,0 +1,60 @@ +// Copyright 2024 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef HashBuilder_h +#define HashBuilder_h + +#include +#include + +#include "HEXBuilder.h" + +class HashBuilder : public HEXBuilder +{ +public: + virtual ~HashBuilder() {} + virtual void begin() = 0; + + virtual void add(uint8_t* data, size_t len) = 0; + virtual void add(const char* data) + { + add((uint8_t*)data, strlen(data)); + } + virtual void add(char* data) + { + add((const char*)data); + } + virtual void add(String data) + { + add(data.c_str()); + } + + virtual void addHexString(const char* data) = 0; + virtual void addHexString(char* data) + { + addHexString((const char*)data); + } + virtual void addHexString(String data) + { + addHexString(data.c_str()); + } + + virtual bool addStream(Stream& stream, const size_t maxLen) = 0; + virtual void calculate() = 0; + virtual void getBytes(uint8_t* output) = 0; + virtual void getChars(char* output) = 0; + virtual String toString() = 0; +}; + +#endif diff --git a/cores/esp32/MD5Builder.cpp b/cores/esp32/MD5Builder.cpp index 8a5d578c337..2198d06a27e 100644 --- a/cores/esp32/MD5Builder.cpp +++ b/cores/esp32/MD5Builder.cpp @@ -16,6 +16,7 @@ License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + #include #include #include @@ -26,14 +27,14 @@ void MD5Builder::begin(void) esp_rom_md5_init(&_ctx); } -void MD5Builder::add(uint8_t * data, uint16_t len) +void MD5Builder::add(uint8_t * data, size_t len) { esp_rom_md5_update(&_ctx, data, len); } void MD5Builder::addHexString(const char * data) { - uint16_t len = strlen(data); + size_t len = strlen(data); uint8_t * tmp = (uint8_t*)malloc(len/2); if(tmp == NULL) { return; diff --git a/cores/esp32/MD5Builder.h b/cores/esp32/MD5Builder.h index ade483a3d2e..25dd8c49ec9 100644 --- a/cores/esp32/MD5Builder.h +++ b/cores/esp32/MD5Builder.h @@ -1,7 +1,7 @@ -/* +/* Copyright (c) 2015 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - + This file is part of the esp32 core for Arduino environment. + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either @@ -15,9 +15,11 @@ You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Modified 10 Jan 2024 by Lucas Saavedra Vaz (Use abstract class HashBuilder) */ -#ifndef __ESP8266_MD5_BUILDER__ -#define __ESP8266_MD5_BUILDER__ +#ifndef MD5Builder_h +#define MD5Builder_h #include #include @@ -25,43 +27,27 @@ #include "esp_system.h" #include "esp_rom_md5.h" -#include "HEXBuilder.h" +#include "HashBuilder.h" -class MD5Builder : HEXBuilder +class MD5Builder : public HashBuilder { private: md5_context_t _ctx; uint8_t _buf[ESP_ROM_MD5_DIGEST_LEN]; public: - void begin(void); - void add(uint8_t * data, uint16_t len); - void add(const char * data) - { - add((uint8_t*)data, strlen(data)); - } - void add(char * data) - { - add((const char*)data); - } - void add(String data) - { - add(data.c_str()); - } - void addHexString(const char * data); - void addHexString(char * data) - { - addHexString((const char*)data); - } - void addHexString(String data) - { - addHexString(data.c_str()); - } - bool addStream(Stream & stream, const size_t maxLen); - void calculate(void); - void getBytes(uint8_t * output); - void getChars(char * output); - String toString(void); -}; + void begin(void) override; + using HashBuilder::add; + void add(uint8_t * data, size_t len) override; + + using HashBuilder::addHexString; + void addHexString(const char * data) override; + + bool addStream(Stream & stream, const size_t maxLen) override; + void calculate(void) override; + void getBytes(uint8_t * output) override; + void getChars(char * output) override; + String toString(void) override; +}; #endif From 81663f58c2656b63fb354c5fa49a02afb858da18 Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Thu, 11 Jan 2024 18:28:34 -0300 Subject: [PATCH 17/21] Add SHA1Builder --- CMakeLists.txt | 3 +- cores/esp32/SHA1Builder.cpp | 367 ++++++++++++++++++ cores/esp32/SHA1Builder.h | 51 +++ .../Utilities/SHA1Builder/SHA1Builder.ino | 97 +++++ 4 files changed, 517 insertions(+), 1 deletion(-) create mode 100644 cores/esp32/SHA1Builder.cpp create mode 100644 cores/esp32/SHA1Builder.h create mode 100644 libraries/ESP32/examples/Utilities/SHA1Builder/SHA1Builder.ino diff --git a/CMakeLists.txt b/CMakeLists.txt index d34fd77a8d3..4935fed5845 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,14 +50,15 @@ set(CORE_SRCS cores/esp32/Esp.cpp cores/esp32/FunctionalInterrupt.cpp cores/esp32/HardwareSerial.cpp + cores/esp32/HEXBuilder.cpp cores/esp32/IPAddress.cpp cores/esp32/IPv6Address.cpp cores/esp32/libb64/cdecode.c cores/esp32/libb64/cencode.c cores/esp32/main.cpp cores/esp32/MD5Builder.cpp - cores/esp32/HEXBuilder.cpp cores/esp32/Print.cpp + cores/esp32/SHA1Builder.cpp cores/esp32/stdlib_noniso.c cores/esp32/Stream.cpp cores/esp32/StreamString.cpp diff --git a/cores/esp32/SHA1Builder.cpp b/cores/esp32/SHA1Builder.cpp new file mode 100644 index 00000000000..8d5e81eec4c --- /dev/null +++ b/cores/esp32/SHA1Builder.cpp @@ -0,0 +1,367 @@ +/* + * FIPS-180-1 compliant SHA-1 implementation + * + * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is part of mbed TLS (https://tls.mbed.org) + * Modified for esp32 by Lucas Saavedra Vaz on 11 Jan 2024 + */ + +#include +#include + +// 32-bit integer manipulation macros (big endian) + +#ifndef GET_UINT32_BE +#define GET_UINT32_BE(n,b,i) \ +{ \ + (n) = ((uint32_t) (b)[(i) ] << 24) \ + | ((uint32_t) (b)[(i) + 1] << 16) \ + | ((uint32_t) (b)[(i) + 2] << 8) \ + | ((uint32_t) (b)[(i) + 3] ); \ +} +#endif + +#ifndef PUT_UINT32_BE +#define PUT_UINT32_BE(n,b,i) \ +{ \ + (b)[(i) ] = (uint8_t) ((n) >> 24); \ + (b)[(i) + 1] = (uint8_t) ((n) >> 16); \ + (b)[(i) + 2] = (uint8_t) ((n) >> 8); \ + (b)[(i) + 3] = (uint8_t) ((n) ); \ +} +#endif + +// Constants + +static const uint8_t sha1_padding[64] = +{ + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +// Private methods + +void SHA1Builder::process(const uint8_t* data) +{ + uint32_t temp, W[16], A, B, C, D, E; + + GET_UINT32_BE(W[ 0], data, 0); + GET_UINT32_BE(W[ 1], data, 4); + GET_UINT32_BE(W[ 2], data, 8); + GET_UINT32_BE(W[ 3], data, 12); + GET_UINT32_BE(W[ 4], data, 16); + GET_UINT32_BE(W[ 5], data, 20); + GET_UINT32_BE(W[ 6], data, 24); + GET_UINT32_BE(W[ 7], data, 28); + GET_UINT32_BE(W[ 8], data, 32); + GET_UINT32_BE(W[ 9], data, 36); + GET_UINT32_BE(W[10], data, 40); + GET_UINT32_BE(W[11], data, 44); + GET_UINT32_BE(W[12], data, 48); + GET_UINT32_BE(W[13], data, 52); + GET_UINT32_BE(W[14], data, 56); + GET_UINT32_BE(W[15], data, 60); + +#define sha1_S(x,n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n))) + +#define sha1_R(t) \ +( \ + temp = W[(t - 3) & 0x0F] ^ W[(t - 8) & 0x0F] ^ \ + W[(t - 14) & 0x0F] ^ W[ t & 0x0F], \ + (W[t & 0x0F] = sha1_S(temp,1)) \ +) + +#define sha1_P(a,b,c,d,e,x) \ +{ \ + e += sha1_S(a,5) + sha1_F(b,c,d) + sha1_K + x; b = sha1_S(b,30); \ +} + + A = state[0]; + B = state[1]; + C = state[2]; + D = state[3]; + E = state[4]; + +#define sha1_F(x,y,z) (z ^ (x & (y ^ z))) +#define sha1_K 0x5A827999 + + sha1_P( A, B, C, D, E, W[0] ); + sha1_P( E, A, B, C, D, W[1] ); + sha1_P( D, E, A, B, C, W[2] ); + sha1_P( C, D, E, A, B, W[3] ); + sha1_P( B, C, D, E, A, W[4] ); + sha1_P( A, B, C, D, E, W[5] ); + sha1_P( E, A, B, C, D, W[6] ); + sha1_P( D, E, A, B, C, W[7] ); + sha1_P( C, D, E, A, B, W[8] ); + sha1_P( B, C, D, E, A, W[9] ); + sha1_P( A, B, C, D, E, W[10] ); + sha1_P( E, A, B, C, D, W[11] ); + sha1_P( D, E, A, B, C, W[12] ); + sha1_P( C, D, E, A, B, W[13] ); + sha1_P( B, C, D, E, A, W[14] ); + sha1_P( A, B, C, D, E, W[15] ); + sha1_P( E, A, B, C, D, sha1_R(16) ); + sha1_P( D, E, A, B, C, sha1_R(17) ); + sha1_P( C, D, E, A, B, sha1_R(18) ); + sha1_P( B, C, D, E, A, sha1_R(19) ); + +#undef sha1_K +#undef sha1_F + +#define sha1_F(x,y,z) (x ^ y ^ z) +#define sha1_K 0x6ED9EBA1 + + sha1_P( A, B, C, D, E, sha1_R(20) ); + sha1_P( E, A, B, C, D, sha1_R(21) ); + sha1_P( D, E, A, B, C, sha1_R(22) ); + sha1_P( C, D, E, A, B, sha1_R(23) ); + sha1_P( B, C, D, E, A, sha1_R(24) ); + sha1_P( A, B, C, D, E, sha1_R(25) ); + sha1_P( E, A, B, C, D, sha1_R(26) ); + sha1_P( D, E, A, B, C, sha1_R(27) ); + sha1_P( C, D, E, A, B, sha1_R(28) ); + sha1_P( B, C, D, E, A, sha1_R(29) ); + sha1_P( A, B, C, D, E, sha1_R(30) ); + sha1_P( E, A, B, C, D, sha1_R(31) ); + sha1_P( D, E, A, B, C, sha1_R(32) ); + sha1_P( C, D, E, A, B, sha1_R(33) ); + sha1_P( B, C, D, E, A, sha1_R(34) ); + sha1_P( A, B, C, D, E, sha1_R(35) ); + sha1_P( E, A, B, C, D, sha1_R(36) ); + sha1_P( D, E, A, B, C, sha1_R(37) ); + sha1_P( C, D, E, A, B, sha1_R(38) ); + sha1_P( B, C, D, E, A, sha1_R(39) ); + +#undef sha1_K +#undef sha1_F + +#define sha1_F(x,y,z) ((x & y) | (z & (x | y))) +#define sha1_K 0x8F1BBCDC + + sha1_P( A, B, C, D, E, sha1_R(40) ); + sha1_P( E, A, B, C, D, sha1_R(41) ); + sha1_P( D, E, A, B, C, sha1_R(42) ); + sha1_P( C, D, E, A, B, sha1_R(43) ); + sha1_P( B, C, D, E, A, sha1_R(44) ); + sha1_P( A, B, C, D, E, sha1_R(45) ); + sha1_P( E, A, B, C, D, sha1_R(46) ); + sha1_P( D, E, A, B, C, sha1_R(47) ); + sha1_P( C, D, E, A, B, sha1_R(48) ); + sha1_P( B, C, D, E, A, sha1_R(49) ); + sha1_P( A, B, C, D, E, sha1_R(50) ); + sha1_P( E, A, B, C, D, sha1_R(51) ); + sha1_P( D, E, A, B, C, sha1_R(52) ); + sha1_P( C, D, E, A, B, sha1_R(53) ); + sha1_P( B, C, D, E, A, sha1_R(54) ); + sha1_P( A, B, C, D, E, sha1_R(55) ); + sha1_P( E, A, B, C, D, sha1_R(56) ); + sha1_P( D, E, A, B, C, sha1_R(57) ); + sha1_P( C, D, E, A, B, sha1_R(58) ); + sha1_P( B, C, D, E, A, sha1_R(59) ); + +#undef sha1_K +#undef sha1_F + +#define sha1_F(x,y,z) (x ^ y ^ z) +#define sha1_K 0xCA62C1D6 + + sha1_P( A, B, C, D, E, sha1_R(60) ); + sha1_P( E, A, B, C, D, sha1_R(61) ); + sha1_P( D, E, A, B, C, sha1_R(62) ); + sha1_P( C, D, E, A, B, sha1_R(63) ); + sha1_P( B, C, D, E, A, sha1_R(64) ); + sha1_P( A, B, C, D, E, sha1_R(65) ); + sha1_P( E, A, B, C, D, sha1_R(66) ); + sha1_P( D, E, A, B, C, sha1_R(67) ); + sha1_P( C, D, E, A, B, sha1_R(68) ); + sha1_P( B, C, D, E, A, sha1_R(69) ); + sha1_P( A, B, C, D, E, sha1_R(70) ); + sha1_P( E, A, B, C, D, sha1_R(71) ); + sha1_P( D, E, A, B, C, sha1_R(72) ); + sha1_P( C, D, E, A, B, sha1_R(73) ); + sha1_P( B, C, D, E, A, sha1_R(74) ); + sha1_P( A, B, C, D, E, sha1_R(75) ); + sha1_P( E, A, B, C, D, sha1_R(76) ); + sha1_P( D, E, A, B, C, sha1_R(77) ); + sha1_P( C, D, E, A, B, sha1_R(78) ); + sha1_P( B, C, D, E, A, sha1_R(79) ); + +#undef sha1_K +#undef sha1_F + + state[0] += A; + state[1] += B; + state[2] += C; + state[3] += D; + state[4] += E; +} + +// Public methods + +void SHA1Builder::begin(void) +{ + total[0] = 0; + total[1] = 0; + + state[0] = 0x67452301; + state[1] = 0xEFCDAB89; + state[2] = 0x98BADCFE; + state[3] = 0x10325476; + state[4] = 0xC3D2E1F0; + + memset(buffer, 0x00, sizeof(buffer)); + memset(hash, 0x00, sizeof(hash)); +} + +void SHA1Builder::add(uint8_t* data, size_t len) +{ + size_t fill; + uint32_t left; + + if(len == 0) + { + return; + } + + left = total[0] & 0x3F; + fill = 64 - left; + + total[0] += (uint32_t) len; + total[0] &= 0xFFFFFFFF; + + if(total[0] < (uint32_t) len) + { + total[1]++; + } + + if(left && len >= fill) + { + memcpy((void *) (buffer + left), data, fill); + process(buffer); + data += fill; + len -= fill; + left = 0; + } + + while(len >= 64) + { + process(data); + data += 64; + len -= 64; + } + + if(len > 0) { + memcpy((void *) (buffer + left), data, len); + } +} + +void SHA1Builder::addHexString(const char * data) +{ + uint16_t len = strlen(data); + uint8_t * tmp = (uint8_t*)malloc(len/2); + if(tmp == NULL) { + return; + } + hex2bytes(tmp, len/2, data); + add(tmp, len/2); + free(tmp); +} + +bool SHA1Builder::addStream(Stream & stream, const size_t maxLen) +{ + const int buf_size = 512; + int maxLengthLeft = maxLen; + uint8_t * buf = (uint8_t*) malloc(buf_size); + + if(!buf) { + return false; + } + + int bytesAvailable = stream.available(); + while((bytesAvailable > 0) && (maxLengthLeft > 0)) { + + // determine number of bytes to read + int readBytes = bytesAvailable; + if(readBytes > maxLengthLeft) { + readBytes = maxLengthLeft ; // read only until max_len + } + if(readBytes > buf_size) { + readBytes = buf_size; // not read more the buffer can handle + } + + // read data and check if we got something + int numBytesRead = stream.readBytes(buf, readBytes); + if(numBytesRead< 1) { + free(buf); + return false; + } + + // Update SHA1 with buffer payload + add(buf, numBytesRead); + + // update available number of bytes + maxLengthLeft -= numBytesRead; + bytesAvailable = stream.available(); + } + free(buf); + return true; +} + +void SHA1Builder::calculate(void) +{ + uint32_t last, padn; + uint32_t high, low; + uint8_t msglen[8]; + + high = (total[0] >> 29) | (total[1] << 3); + low = (total[0] << 3); + + PUT_UINT32_BE(high, msglen, 0); + PUT_UINT32_BE(low, msglen, 4); + + last = total[0] & 0x3F; + padn = (last < 56) ? (56 - last) : (120 - last); + + add((uint8_t*)sha1_padding, padn); + add(msglen, 8); + + PUT_UINT32_BE(state[0], hash, 0); + PUT_UINT32_BE(state[1], hash, 4); + PUT_UINT32_BE(state[2], hash, 8); + PUT_UINT32_BE(state[3], hash, 12); + PUT_UINT32_BE(state[4], hash, 16); +} + +void SHA1Builder::getBytes(uint8_t * output) +{ + memcpy(output, hash, SHA1_HASH_SIZE); +} + +void SHA1Builder::getChars(char * output) +{ + bytes2hex(output, SHA1_HASH_SIZE*2+1, hash, SHA1_HASH_SIZE); +} + +String SHA1Builder::toString(void) +{ + char out[(SHA1_HASH_SIZE * 2) + 1]; + getChars(out); + return String(out); +} diff --git a/cores/esp32/SHA1Builder.h b/cores/esp32/SHA1Builder.h new file mode 100644 index 00000000000..4a0dfe0c100 --- /dev/null +++ b/cores/esp32/SHA1Builder.h @@ -0,0 +1,51 @@ +// Copyright 2024 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SHA1Builder_h +#define SHA1Builder_h + +#include +#include + +#include "HashBuilder.h" + +#define SHA1_HASH_SIZE 20 + +class SHA1Builder : public HashBuilder +{ +private: + uint32_t total[2]; /* number of bytes processed */ + uint32_t state[5]; /* intermediate digest state */ + unsigned char buffer[64]; /* data block being processed */ + uint8_t hash[SHA1_HASH_SIZE]; /* SHA-1 result */ + + void process(const uint8_t* data); + +public: + void begin() override; + + using HashBuilder::add; + void add(uint8_t* data, size_t len) override; + + using HashBuilder::addHexString; + void addHexString(const char* data) override; + + bool addStream(Stream& stream, const size_t maxLen) override; + void calculate() override; + void getBytes(uint8_t* output) override; + void getChars(char* output) override; + String toString() override; +}; + +#endif diff --git a/libraries/ESP32/examples/Utilities/SHA1Builder/SHA1Builder.ino b/libraries/ESP32/examples/Utilities/SHA1Builder/SHA1Builder.ino new file mode 100644 index 00000000000..40270953473 --- /dev/null +++ b/libraries/ESP32/examples/Utilities/SHA1Builder/SHA1Builder.ino @@ -0,0 +1,97 @@ +#include + +// Occasionally it is useful to compare a password that the user +// has entered to a build in string. However this means that the +// password ends up `in the clear' in the firmware and in your +// source code. +// +// SHA1Builder helps you obfuscate this (This is not much more secure. +// SHA1 is past its retirement age and long obsolte/insecure, but it helps +// a little) by letting you create an (unsalted!) SHA1 of the data the +// user entered; and then compare this to an SHA1 string that you have put +// in your code. + +void setup() { + Serial.begin(115200); + while (!Serial) { delay(10); } + Serial.println("\n\n\nStart."); + + // Check if a password obfuscated in an SHA1 actually + // matches the original string. + // + // echo -n "Hello World" | openssl sha1 + { + String sha1_str = "0a4d55a8d778e5022fab701977c5d840bbc486d0"; + String password = "Hello World"; + + SHA1Builder sha; + + sha.begin(); + sha.add(password); + sha.calculate(); + + String result = sha.toString(); + + if (!sha1_str.equalsIgnoreCase(result)) + Serial.println("Odd - failing SHA1 on String"); + else + Serial.println("OK!"); + } + + // Check that this also work if we add the password not as + // a normal string - but as a string with the HEX values. + { + String passwordAsHex = "48656c6c6f20576f726c64"; + String sha1_str = "0a4d55a8d778e5022fab701977c5d840bbc486d0"; + + SHA1Builder sha; + + sha.begin(); + sha.addHexString(passwordAsHex); + sha.calculate(); + + String result = sha.toString(); + + if (!sha1_str.equalsIgnoreCase(result)) { + Serial.println("Odd - failing SHA1 on hex string"); + Serial.println(sha1_str); + Serial.println(result); + } + else + Serial.println("OK!"); + + } + + // Check that this also work if we add the password as + // an unsigned byte array. + { + uint8_t password[] = { 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64 }; + String sha1_str = "0a4d55a8d778e5022fab701977c5d840bbc486d0"; + SHA1Builder sha; + + sha.begin(); + sha.add(password, sizeof(password)); + sha.calculate(); + + String result = sha.toString(); + + if (!sha1_str.equalsIgnoreCase(result)) + Serial.println("Odd - failing SHA1 on byte array"); + else + Serial.println("OK!"); + + // And also check that we can compare this as pure, raw, bytes + uint8_t raw[20] = { 0x0a, 0x4d, 0x55, 0xa8, 0xd7, 0x78, 0xe5, 0x02, 0x2f, 0xab, + 0x70, 0x19, 0x77, 0xc5, 0xd8, 0x40, 0xbb, 0xc4, 0x86, 0xd0 + }; + uint8_t res[20]; + sha.getBytes(res); + if (memcmp(raw, res, 20)) + Serial.println("Odd - failing SHA1 on byte array when compared as bytes"); + else + Serial.println("OK!"); + + } +} + +void loop() {} From 85b2f68082518b2fee45f5b640db6be238b18822 Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Thu, 11 Jan 2024 18:31:35 -0300 Subject: [PATCH 18/21] Fix comment --- cores/esp32/HEXBuilder.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cores/esp32/HEXBuilder.h b/cores/esp32/HEXBuilder.h index d4d68e8c45d..b3ec02ae267 100644 --- a/cores/esp32/HEXBuilder.h +++ b/cores/esp32/HEXBuilder.h @@ -1,6 +1,6 @@ /* Copyright (c) 2015 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. + This file is part of the esp32 core for Arduino environment. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -16,6 +16,7 @@ License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + #ifndef HEXBuilder_h #define HEXBuilder_h From 9ff23e70b4f268b9a1e44d413275ed01055af8d9 Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Thu, 11 Jan 2024 22:40:25 -0300 Subject: [PATCH 19/21] Fix whitespace --- cores/esp32/SHA1Builder.cpp | 160 ++++++++++++++++++------------------ 1 file changed, 80 insertions(+), 80 deletions(-) diff --git a/cores/esp32/SHA1Builder.cpp b/cores/esp32/SHA1Builder.cpp index 8d5e81eec4c..34f93271321 100644 --- a/cores/esp32/SHA1Builder.cpp +++ b/cores/esp32/SHA1Builder.cpp @@ -101,26 +101,26 @@ void SHA1Builder::process(const uint8_t* data) #define sha1_F(x,y,z) (z ^ (x & (y ^ z))) #define sha1_K 0x5A827999 - sha1_P( A, B, C, D, E, W[0] ); - sha1_P( E, A, B, C, D, W[1] ); - sha1_P( D, E, A, B, C, W[2] ); - sha1_P( C, D, E, A, B, W[3] ); - sha1_P( B, C, D, E, A, W[4] ); - sha1_P( A, B, C, D, E, W[5] ); - sha1_P( E, A, B, C, D, W[6] ); - sha1_P( D, E, A, B, C, W[7] ); - sha1_P( C, D, E, A, B, W[8] ); - sha1_P( B, C, D, E, A, W[9] ); - sha1_P( A, B, C, D, E, W[10] ); - sha1_P( E, A, B, C, D, W[11] ); - sha1_P( D, E, A, B, C, W[12] ); - sha1_P( C, D, E, A, B, W[13] ); - sha1_P( B, C, D, E, A, W[14] ); - sha1_P( A, B, C, D, E, W[15] ); - sha1_P( E, A, B, C, D, sha1_R(16) ); - sha1_P( D, E, A, B, C, sha1_R(17) ); - sha1_P( C, D, E, A, B, sha1_R(18) ); - sha1_P( B, C, D, E, A, sha1_R(19) ); + sha1_P(A, B, C, D, E, W[0]); + sha1_P(E, A, B, C, D, W[1]); + sha1_P(D, E, A, B, C, W[2]); + sha1_P(C, D, E, A, B, W[3]); + sha1_P(B, C, D, E, A, W[4]); + sha1_P(A, B, C, D, E, W[5]); + sha1_P(E, A, B, C, D, W[6]); + sha1_P(D, E, A, B, C, W[7]); + sha1_P(C, D, E, A, B, W[8]); + sha1_P(B, C, D, E, A, W[9]); + sha1_P(A, B, C, D, E, W[10]); + sha1_P(E, A, B, C, D, W[11]); + sha1_P(D, E, A, B, C, W[12]); + sha1_P(C, D, E, A, B, W[13]); + sha1_P(B, C, D, E, A, W[14]); + sha1_P(A, B, C, D, E, W[15]); + sha1_P(E, A, B, C, D, sha1_R(16)); + sha1_P(D, E, A, B, C, sha1_R(17)); + sha1_P(C, D, E, A, B, sha1_R(18)); + sha1_P(B, C, D, E, A, sha1_R(19)); #undef sha1_K #undef sha1_F @@ -128,26 +128,26 @@ void SHA1Builder::process(const uint8_t* data) #define sha1_F(x,y,z) (x ^ y ^ z) #define sha1_K 0x6ED9EBA1 - sha1_P( A, B, C, D, E, sha1_R(20) ); - sha1_P( E, A, B, C, D, sha1_R(21) ); - sha1_P( D, E, A, B, C, sha1_R(22) ); - sha1_P( C, D, E, A, B, sha1_R(23) ); - sha1_P( B, C, D, E, A, sha1_R(24) ); - sha1_P( A, B, C, D, E, sha1_R(25) ); - sha1_P( E, A, B, C, D, sha1_R(26) ); - sha1_P( D, E, A, B, C, sha1_R(27) ); - sha1_P( C, D, E, A, B, sha1_R(28) ); - sha1_P( B, C, D, E, A, sha1_R(29) ); - sha1_P( A, B, C, D, E, sha1_R(30) ); - sha1_P( E, A, B, C, D, sha1_R(31) ); - sha1_P( D, E, A, B, C, sha1_R(32) ); - sha1_P( C, D, E, A, B, sha1_R(33) ); - sha1_P( B, C, D, E, A, sha1_R(34) ); - sha1_P( A, B, C, D, E, sha1_R(35) ); - sha1_P( E, A, B, C, D, sha1_R(36) ); - sha1_P( D, E, A, B, C, sha1_R(37) ); - sha1_P( C, D, E, A, B, sha1_R(38) ); - sha1_P( B, C, D, E, A, sha1_R(39) ); + sha1_P(A, B, C, D, E, sha1_R(20)); + sha1_P(E, A, B, C, D, sha1_R(21)); + sha1_P(D, E, A, B, C, sha1_R(22)); + sha1_P(C, D, E, A, B, sha1_R(23)); + sha1_P(B, C, D, E, A, sha1_R(24)); + sha1_P(A, B, C, D, E, sha1_R(25)); + sha1_P(E, A, B, C, D, sha1_R(26)); + sha1_P(D, E, A, B, C, sha1_R(27)); + sha1_P(C, D, E, A, B, sha1_R(28)); + sha1_P(B, C, D, E, A, sha1_R(29)); + sha1_P(A, B, C, D, E, sha1_R(30)); + sha1_P(E, A, B, C, D, sha1_R(31)); + sha1_P(D, E, A, B, C, sha1_R(32)); + sha1_P(C, D, E, A, B, sha1_R(33)); + sha1_P(B, C, D, E, A, sha1_R(34)); + sha1_P(A, B, C, D, E, sha1_R(35)); + sha1_P(E, A, B, C, D, sha1_R(36)); + sha1_P(D, E, A, B, C, sha1_R(37)); + sha1_P(C, D, E, A, B, sha1_R(38)); + sha1_P(B, C, D, E, A, sha1_R(39)); #undef sha1_K #undef sha1_F @@ -155,26 +155,26 @@ void SHA1Builder::process(const uint8_t* data) #define sha1_F(x,y,z) ((x & y) | (z & (x | y))) #define sha1_K 0x8F1BBCDC - sha1_P( A, B, C, D, E, sha1_R(40) ); - sha1_P( E, A, B, C, D, sha1_R(41) ); - sha1_P( D, E, A, B, C, sha1_R(42) ); - sha1_P( C, D, E, A, B, sha1_R(43) ); - sha1_P( B, C, D, E, A, sha1_R(44) ); - sha1_P( A, B, C, D, E, sha1_R(45) ); - sha1_P( E, A, B, C, D, sha1_R(46) ); - sha1_P( D, E, A, B, C, sha1_R(47) ); - sha1_P( C, D, E, A, B, sha1_R(48) ); - sha1_P( B, C, D, E, A, sha1_R(49) ); - sha1_P( A, B, C, D, E, sha1_R(50) ); - sha1_P( E, A, B, C, D, sha1_R(51) ); - sha1_P( D, E, A, B, C, sha1_R(52) ); - sha1_P( C, D, E, A, B, sha1_R(53) ); - sha1_P( B, C, D, E, A, sha1_R(54) ); - sha1_P( A, B, C, D, E, sha1_R(55) ); - sha1_P( E, A, B, C, D, sha1_R(56) ); - sha1_P( D, E, A, B, C, sha1_R(57) ); - sha1_P( C, D, E, A, B, sha1_R(58) ); - sha1_P( B, C, D, E, A, sha1_R(59) ); + sha1_P(A, B, C, D, E, sha1_R(40)); + sha1_P(E, A, B, C, D, sha1_R(41)); + sha1_P(D, E, A, B, C, sha1_R(42)); + sha1_P(C, D, E, A, B, sha1_R(43)); + sha1_P(B, C, D, E, A, sha1_R(44)); + sha1_P(A, B, C, D, E, sha1_R(45)); + sha1_P(E, A, B, C, D, sha1_R(46)); + sha1_P(D, E, A, B, C, sha1_R(47)); + sha1_P(C, D, E, A, B, sha1_R(48)); + sha1_P(B, C, D, E, A, sha1_R(49)); + sha1_P(A, B, C, D, E, sha1_R(50)); + sha1_P(E, A, B, C, D, sha1_R(51)); + sha1_P(D, E, A, B, C, sha1_R(52)); + sha1_P(C, D, E, A, B, sha1_R(53)); + sha1_P(B, C, D, E, A, sha1_R(54)); + sha1_P(A, B, C, D, E, sha1_R(55)); + sha1_P(E, A, B, C, D, sha1_R(56)); + sha1_P(D, E, A, B, C, sha1_R(57)); + sha1_P(C, D, E, A, B, sha1_R(58)); + sha1_P(B, C, D, E, A, sha1_R(59)); #undef sha1_K #undef sha1_F @@ -182,26 +182,26 @@ void SHA1Builder::process(const uint8_t* data) #define sha1_F(x,y,z) (x ^ y ^ z) #define sha1_K 0xCA62C1D6 - sha1_P( A, B, C, D, E, sha1_R(60) ); - sha1_P( E, A, B, C, D, sha1_R(61) ); - sha1_P( D, E, A, B, C, sha1_R(62) ); - sha1_P( C, D, E, A, B, sha1_R(63) ); - sha1_P( B, C, D, E, A, sha1_R(64) ); - sha1_P( A, B, C, D, E, sha1_R(65) ); - sha1_P( E, A, B, C, D, sha1_R(66) ); - sha1_P( D, E, A, B, C, sha1_R(67) ); - sha1_P( C, D, E, A, B, sha1_R(68) ); - sha1_P( B, C, D, E, A, sha1_R(69) ); - sha1_P( A, B, C, D, E, sha1_R(70) ); - sha1_P( E, A, B, C, D, sha1_R(71) ); - sha1_P( D, E, A, B, C, sha1_R(72) ); - sha1_P( C, D, E, A, B, sha1_R(73) ); - sha1_P( B, C, D, E, A, sha1_R(74) ); - sha1_P( A, B, C, D, E, sha1_R(75) ); - sha1_P( E, A, B, C, D, sha1_R(76) ); - sha1_P( D, E, A, B, C, sha1_R(77) ); - sha1_P( C, D, E, A, B, sha1_R(78) ); - sha1_P( B, C, D, E, A, sha1_R(79) ); + sha1_P(A, B, C, D, E, sha1_R(60)); + sha1_P(E, A, B, C, D, sha1_R(61)); + sha1_P(D, E, A, B, C, sha1_R(62)); + sha1_P(C, D, E, A, B, sha1_R(63)); + sha1_P(B, C, D, E, A, sha1_R(64)); + sha1_P(A, B, C, D, E, sha1_R(65)); + sha1_P(E, A, B, C, D, sha1_R(66)); + sha1_P(D, E, A, B, C, sha1_R(67)); + sha1_P(C, D, E, A, B, sha1_R(68)); + sha1_P(B, C, D, E, A, sha1_R(69)); + sha1_P(A, B, C, D, E, sha1_R(70)); + sha1_P(E, A, B, C, D, sha1_R(71)); + sha1_P(D, E, A, B, C, sha1_R(72)); + sha1_P(C, D, E, A, B, sha1_R(73)); + sha1_P(B, C, D, E, A, sha1_R(74)); + sha1_P(A, B, C, D, E, sha1_R(75)); + sha1_P(E, A, B, C, D, sha1_R(76)); + sha1_P(D, E, A, B, C, sha1_R(77)); + sha1_P(C, D, E, A, B, sha1_R(78)); + sha1_P(B, C, D, E, A, sha1_R(79)); #undef sha1_K #undef sha1_F From 5631cf477339561e8d88fe35fe4d1664ca2ee621 Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Mon, 15 Jan 2024 22:57:08 -0300 Subject: [PATCH 20/21] Fix crashes and improve log messages --- .../HttpBasicAuthSHA1orBearerToken.ino | 49 ++-- libraries/WebServer/src/WebServer.cpp | 220 ++++++++++-------- libraries/WebServer/src/WebServer.h | 8 +- 3 files changed, 159 insertions(+), 118 deletions(-) diff --git a/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino b/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino index 9cec9f5c95c..ecf04031181 100644 --- a/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino +++ b/libraries/WebServer/examples/HttpBasicAuthSHA1orBearerToken/HttpBasicAuthSHA1orBearerToken.ino @@ -2,7 +2,7 @@ #include #include #include -#include "mbedtls/sha1.h" +#include // We have two options - we either come in with a bearer // token - i.e. a special header or API token; or we @@ -15,7 +15,7 @@ // We avoid hardcoding this "SecritToken" into the code by // using a SHA1 instead (which is not paricularly secure). -// Create the secet token SHA1 with: +// Create the secret token SHA1 with: // echo -n SecritToken | openssl sha1 String secret_token_hex = "d2cce6b472959484a21c3194080c609b8a2c910b"; @@ -35,16 +35,12 @@ WebServer server(80); // The sha1 of 'esp32' (without the trailing \0) expressed as 20 // bytes of hex. Created by for example 'echo -n esp32 | openssl sha1' // or http://www.sha1-online.com. -const char* www_username_hex = "hexadmin"; +const char* www_username_hex = "admin"; const char* www_password_hex = "8cb124f8c277c16ec0b2ee00569fd151a08e342b"; -// The same; but now expressed as a base64 string (e.g. as commonly used -// by webservers). Created by ` echo -n esp32 | openssl sha1 -binary | openssl base64` -const char* www_username_base64 = "base64admin"; -const char* www_password_base64 = "jLEk+MJ3wW7Asu4AVp/RUaCONCs="; - static unsigned char _bearer[20]; -String * check_bearer_or_auth(HTTPAuthMethod mode, String authReq, String params[]) { + +String* check_bearer_or_auth(HTTPAuthMethod mode, String authReq, String params[]) { // we expect authReq to be "bearer some-secret" String lcAuthReq = authReq; lcAuthReq.toLowerCase(); @@ -53,15 +49,31 @@ String * check_bearer_or_auth(HTTPAuthMethod mode, String authReq, String params secret.trim(); uint8_t sha1[20]; - mbedtls_sha1((const uint8_t*) secret.c_str(), secret.length(), sha1); - - if (0 == memcpy(_bearer, sha1, sizeof(_bearer))) - return new String("anything non null"); - }; + SHA1Builder sha_builder; + + sha_builder.begin(); + sha_builder.add((uint8_t*) secret.c_str(), secret.length()); + sha_builder.calculate(); + sha_builder.getBytes(sha1); + + if (memcmp(_bearer, sha1, sizeof(_bearer)) == 0) { + Serial.println("Bearer token matches"); + return new String("anything non null"); + } else { + Serial.println("Bearer token does not match"); + } + } else if (mode == BASIC_AUTH) { + bool ret = server.authenticateBasicSHA1(www_username_hex, www_password_hex); + if (ret) { + Serial.println("Basic auth succeeded"); + return new String(params[0]); + } else { + Serial.println("Basic auth failed"); + } + } -// that failed - so do a normal auth -return server.authenticateBasicSHA1(www_username_hex, www_password_hex) ? - new String(params[0]) : NULL; + // No auth found + return NULL; }; void setup() { @@ -77,7 +89,7 @@ void setup() { ArduinoOTA.begin(); // Convert token to a convenient binary representation. - size_t len = HEXBuilder::hex2bytes(_bearer,sizeof(_bearer),secret_token_hex); + size_t len = HEXBuilder::hex2bytes(_bearer, sizeof(_bearer), secret_token_hex); if (len != 20) Serial.println("Bearer token does not look like a valid SHA1 hex string ?!"); @@ -86,6 +98,7 @@ void setup() { Serial.println("No/failed authentication"); return server.requestAuthentication(); } + Serial.println("Authentication succeeded"); server.send(200, "text/plain", "Login OK"); return; }); diff --git a/libraries/WebServer/src/WebServer.cpp b/libraries/WebServer/src/WebServer.cpp index 780a753822b..26a5a967058 100644 --- a/libraries/WebServer/src/WebServer.cpp +++ b/libraries/WebServer/src/WebServer.cpp @@ -29,12 +29,11 @@ #include "WiFiServer.h" #include "WiFiClient.h" #include "WebServer.h" -#include "HEXBuilder.h" #include "FS.h" #include "detail/RequestHandlersImpl.h" #include "MD5Builder.h" -#include "mbedtls/sha1.h" -#include "mbedtls/base64.h" +#include "SHA1Builder.h" +#include "base64.h" static const char AUTHORIZATION_HEADER[] = "Authorization"; static const char qop_auth[] PROGMEM = "qop=auth"; @@ -131,155 +130,184 @@ static String md5str(String &in){ } bool WebServer::authenticateBasicSHA1(const char * _username, const char * _sha1Base64orHex) { - return WebServer::authenticate([_username,_sha1Base64orHex](HTTPAuthMethod mode, String username, String params[]) -> String * { - // rather than work on a password to compare with; we take the sha1 of the - // password received over the wire and compare that to the base64 encoded - // sha1 passed as _sha1base64. That way there is no need to have a - // plaintext password in the code/binary (though note that SHA1 is well - // past its retirement age). When that matches - we `cheat' by returning - // the password we got in the first place; so the normal BasicAuth - // can be completed. Note that this cannot work for a digest auth - - // as there the password in the clear is part of the calculation. - uint8_t sha1[20]; - char sha1calc[48]; // large enough for base64 and Hex represenation - size_t olen = 0; - int ret = 0; - - mbedtls_sha1((const uint8_t*) params[0].c_str(),params[0].length(),sha1); - - // we can either decode _sha1base64orHex and then compare the 20 bytes; - // or encode the sha we calculated. We pick the latter as encoding of a - // fixed array of 20 bytes s safer than operating on something external. - #define _H2D(x) (((x)>='0' && ((x) <='9')) ? ((x)-'0') : (((x)>='a' && (x)<='f') ? ((x)-'a'+10) : 0)) - #define H2D(x) (_H2D(tolower((x)))) - if (strlen(_sha1Base64orHex) == 20 * 2) { - HEXBuilder::bytes2hex(sha1calc,sizeof(sha1calc),sha1,sizeof(sha1)); - } else { - ret = mbedtls_base64_encode((uint8_t*)sha1calc, sizeof(sha1calc), &olen, sha1, sizeof(sha1)); - } - - return ((username.equalsConstantTime(_username)) && - (String((char*)sha1calc).equalsConstantTime(_sha1Base64orHex)) && - (ret == 0) && - (mode == BASIC_AUTH) /* to keep things somewhat time constant. */ - ) ? new String(params[0]) : NULL; - }); + return WebServer::authenticate([_username,_sha1Base64orHex](HTTPAuthMethod mode, String username, String params[])->String * { + // rather than work on a password to compare with; we take the sha1 of the + // password received over the wire and compare that to the base64 encoded + // sha1 passed as _sha1base64. That way there is no need to have a + // plaintext password in the code/binary (though note that SHA1 is well + // past its retirement age). When that matches - we `cheat' by returning + // the password we got in the first place; so the normal BasicAuth + // can be completed. Note that this cannot work for a digest auth - + // as there the password in the clear is part of the calculation. + + if (params == nullptr) { + log_e("Something went wrong. params is NULL"); + return NULL; + } + + uint8_t sha1[20]; + char sha1calc[48]; // large enough for base64 and Hex represenation + String ret; + SHA1Builder sha_builder; + base64 b64; + + log_v("Trying to authenticate user %s using SHA1.", username.c_str()); + sha_builder.begin(); + sha_builder.add((uint8_t*) params[0].c_str(), params[0].length()); + sha_builder.calculate(); + sha_builder.getBytes(sha1); + + // we can either decode _sha1base64orHex and then compare the 20 bytes; + // or encode the sha we calculated. We pick the latter as encoding of a + // fixed array of 20 bytes is safer than operating on something external. + if (strlen(_sha1Base64orHex) == 20 * 2) { // 2 chars per byte + sha_builder.bytes2hex(sha1calc, sizeof(sha1calc), sha1, sizeof(sha1)); + log_v("Calculated SHA1 in hex: %s", sha1calc); + } else { + ret = b64.encode(sha1, sizeof(sha1)); + ret.toCharArray(sha1calc, sizeof(sha1calc)); + log_v("Calculated SHA1 in base64: %s", sha1calc); + } + + return ((username.equalsConstantTime(_username)) && + (String((char*)sha1calc).equalsConstantTime(_sha1Base64orHex)) && + (mode == BASIC_AUTH) /* to keep things somewhat time constant. */ + ) ? new String(params[0]) : NULL; +}); } bool WebServer::authenticate(const char * _username, const char * _password){ - return WebServer::authenticate([_username,_password](HTTPAuthMethod mode, String username, String params[]) -> String * { - return username.equalsConstantTime(_username) ? new String(_password) : NULL; - }); + return WebServer::authenticate([_username,_password](HTTPAuthMethod mode, String username, String params[])->String * { + return username.equalsConstantTime(_username) ? new String(_password) : NULL; + }); } bool WebServer::authenticate(THandlerFunctionAuthCheck fn) { - if(!hasHeader(FPSTR(AUTHORIZATION_HEADER))) + if (!hasHeader(FPSTR(AUTHORIZATION_HEADER))) { return false; + } String authReq = header(FPSTR(AUTHORIZATION_HEADER)); - if(authReq.startsWith(AuthTypeBasic)) { + if (authReq.startsWith(AuthTypeBasic)) { + log_v("Trying to authenticate using Basic Auth"); bool ret = false; authReq = authReq.substring(6); // length of AuthTypeBasic including the space at the end. authReq.trim(); /* base64 encoded string is always shorter (or equal) in length */ - char *decoded = (authReq.length() < HTTP_MAX_BASIC_AUTH_LEN) ? new char[authReq.length()] : NULL; + char* decoded = (authReq.length() < HTTP_MAX_BASIC_AUTH_LEN) ? new char[authReq.length()] : NULL; if (decoded) { - char * p; - if (base64_decode_chars(authReq.c_str(), authReq.length(), decoded) && (p = index(decoded,':')) && p) { - authReq = ""; - /* Note: rfc7617 guarantees that there will not be an escaped colon in the username itself. - * Note: base64_decode_chars() guarantees a terminating \0 - */ - *p = '\0'; - char * _username = decoded, *_password = p+1; - String params[] = { _password, _srealm }; - String * password = fn(BASIC_AUTH, _username, params); - if (password) { - ret = password->equalsConstantTime(_password); - // we're more concerned about the password; as the attacker already - // knows the _pasword. Arduino's string handling is simple; it reallocs - // even when smaller; so a memset is enough (no capacity/size). - memset((void*)password->c_str(),0,password->length()); - delete password; - }; - }; + char* p; + if (base64_decode_chars(authReq.c_str(), authReq.length(), decoded) && (p = index(decoded, ':')) && p) { + authReq = ""; + /* Note: rfc7617 guarantees that there will not be an escaped colon in the username itself. + * Note: base64_decode_chars() guarantees a terminating \0 + */ + *p = '\0'; + char * _username = decoded, * _password = p + 1; + String params[] = { + _password, + _srealm + }; + String* password = fn(BASIC_AUTH, _username, params); + + if (password) { + ret = password->equalsConstantTime(_password); + // we're more concerned about the password; as the attacker already + // knows the _pasword. Arduino's string handling is simple; it reallocs + // even when smaller; so a memset is enough (no capacity/size). + memset((void *)password->c_str(), 0, password->length()); + delete password; + } + } delete[] decoded; - }; + } authReq = ""; + log_v("Authentication %s", ret ? "Success" : "Failed"); return ret; - } else if(authReq.startsWith(AuthTypeDigest)) { + } else if (authReq.startsWith(AuthTypeDigest)) { + log_v("Trying to authenticate using Digest Auth"); authReq = authReq.substring(7); log_v("%s", authReq.c_str()); // extracting required parameters for RFC 2069 simpler Digest - String _username = _extractParam(authReq,F("username=\""),'\"'); - String _realm = _extractParam(authReq, F("realm=\""),'\"'); - String _uri = _extractParam(authReq, F("uri=\""),'\"'); + String _username = _extractParam(authReq, F("username=\""), '\"'); + String _realm = _extractParam(authReq, F("realm=\""), '\"'); + String _uri = _extractParam(authReq, F("uri=\""), '\"'); if (!_username.length()) goto exf; - String params[] = { _realm, _uri }; - String * password = fn(DIGEST_AUTH,_username, params); - if(!password) + String params[] = { + _realm, + _uri + }; + String* password = fn(DIGEST_AUTH, _username, params); + if (!password) goto exf; - String _H1 = md5str(String(_username) + ':' + _realm + ':' + *password); + String _H1 = md5str(String(_username) + ':' + _realm + ':' + * password); // we're extra concerned; as digest request us to know the password // in the clear. - memset((void*)password->c_str(),0,password->length()); + memset((void *) password->c_str(), 0, password->length()); delete password; _username = ""; - String _nonce = _extractParam(authReq, F("nonce=\""),'\"'); - String _response = _extractParam(authReq, F("response=\""),'\"'); - String _opaque = _extractParam(authReq, F("opaque=\""),'\"'); + String _nonce = _extractParam(authReq, F("nonce=\""), '\"'); + String _response = _extractParam(authReq, F("response=\""), '\"'); + String _opaque = _extractParam(authReq, F("opaque=\""), '\"'); - if((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_response.length()) || (!_opaque.length())) + if ((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_response.length()) || (!_opaque.length())) goto exf; - if((_opaque != _sopaque) || (_nonce != _snonce) || (_realm != _srealm)) + if ((_opaque != _sopaque) || (_nonce != _snonce) || (_realm != _srealm)) goto exf; // parameters for the RFC 2617 newer Digest - String _nc,_cnonce; - if(authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) { + String _nc, _cnonce; + if (authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) { _nc = _extractParam(authReq, F("nc="), ','); - _cnonce = _extractParam(authReq, F("cnonce=\""),'\"'); + _cnonce = _extractParam(authReq, F("cnonce=\""), '\"'); } log_v("Hash of user:realm:pass=%s", _H1.c_str()); String _H2 = ""; - if(_currentMethod == HTTP_GET){ - _H2 = md5str(String(F("GET:")) + _uri); - }else if(_currentMethod == HTTP_POST){ - _H2 = md5str(String(F("POST:")) + _uri); - }else if(_currentMethod == HTTP_PUT){ - _H2 = md5str(String(F("PUT:")) + _uri); - }else if(_currentMethod == HTTP_DELETE){ - _H2 = md5str(String(F("DELETE:")) + _uri); - }else{ - _H2 = md5str(String(F("GET:")) + _uri); + if (_currentMethod == HTTP_GET) { + _H2 = md5str(String(F("GET:")) + _uri); + } else if (_currentMethod == HTTP_POST) { + _H2 = md5str(String(F("POST:")) + _uri); + } else if (_currentMethod == HTTP_PUT) { + _H2 = md5str(String(F("PUT:")) + _uri); + } else if (_currentMethod == HTTP_DELETE) { + _H2 = md5str(String(F("DELETE:")) + _uri); + } else { + _H2 = md5str(String(F("GET:")) + _uri); } log_v("Hash of GET:uri=%s", _H2.c_str()); String _responsecheck = ""; - if(authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) { - _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2); + if (authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) { + _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2); } else { - _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2); + _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2); } authReq = ""; log_v("The Proper response=%s", _responsecheck.c_str()); - return _response == _responsecheck; + bool ret = _response == _responsecheck; + log_v("Authentication %s", ret ? "Success" : "Failed"); + return ret; } else if (authReq.length()) { - String * ret = fn(OTHER_AUTH, authReq, {}); - if (ret) - return true; + // OTHER_AUTH + log_v("Trying to authenticate using Other Auth, authReq=%s", authReq.c_str()); + String* ret = fn(OTHER_AUTH, authReq, {}); + if (ret) { + log_v("Authentication Success"); + return true; + } } exf: authReq = ""; + log_v("Authentication Failed"); return false; } diff --git a/libraries/WebServer/src/WebServer.h b/libraries/WebServer/src/WebServer.h index 95f36aac9d1..d668c4e70e7 100644 --- a/libraries/WebServer/src/WebServer.h +++ b/libraries/WebServer/src/WebServer.h @@ -86,12 +86,12 @@ class WebServer const String AuthTypeDigest = F("Digest"); const String AuthTypeBasic = F("Basic"); - /* Callbackhandler for authentication. The extra parameters depend on the + /* Callbackhandler for authentication. The extra parameters depend on the * HTTPAuthMethod mode: * - * BASIC_AUTH enteredUsernameOrReq contains the username entered by the user - * param[0] password entered (in the clear) - * param[1] authentication realm. + * BASIC_AUTH enteredUsernameOrReq contains the username entered by the user + * param[0] password entered (in the clear) + * param[1] authentication realm. * * To return - the password the user entered password is compared to. Or Null on fail. * From 05fd5c51034f2a765005ff38013d74f563eb52fa Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Tue, 16 Jan 2024 09:40:54 -0300 Subject: [PATCH 21/21] Fix indentation for webserver --- libraries/WebServer/src/WebServer.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/libraries/WebServer/src/WebServer.cpp b/libraries/WebServer/src/WebServer.cpp index 26a5a967058..dd443dbd8e8 100644 --- a/libraries/WebServer/src/WebServer.cpp +++ b/libraries/WebServer/src/WebServer.cpp @@ -130,7 +130,7 @@ static String md5str(String &in){ } bool WebServer::authenticateBasicSHA1(const char * _username, const char * _sha1Base64orHex) { - return WebServer::authenticate([_username,_sha1Base64orHex](HTTPAuthMethod mode, String username, String params[])->String * { + return WebServer::authenticate([_username,_sha1Base64orHex](HTTPAuthMethod mode, String username, String params[])->String * { // rather than work on a password to compare with; we take the sha1 of the // password received over the wire and compare that to the base64 encoded // sha1 passed as _sha1base64. That way there is no need to have a @@ -173,11 +173,11 @@ bool WebServer::authenticateBasicSHA1(const char * _username, const char * _sha1 (String((char*)sha1calc).equalsConstantTime(_sha1Base64orHex)) && (mode == BASIC_AUTH) /* to keep things somewhat time constant. */ ) ? new String(params[0]) : NULL; -}); + }); } -bool WebServer::authenticate(const char * _username, const char * _password){ - return WebServer::authenticate([_username,_password](HTTPAuthMethod mode, String username, String params[])->String * { +bool WebServer::authenticate(const char * _username, const char * _password) { + return WebServer::authenticate([_username,_password](HTTPAuthMethod mode, String username, String params[])->String * { return username.equalsConstantTime(_username) ? new String(_password) : NULL; }); } @@ -638,9 +638,9 @@ String WebServer::pathArg(unsigned int i) { String WebServer::arg(String name) { for (int j = 0; j < _postArgsLen; ++j) { - if ( _postArgs[j].key == name ) - return _postArgs[j].value; - } + if ( _postArgs[j].key == name ) + return _postArgs[j].value; + } for (int i = 0; i < _currentArgCount; ++i) { if ( _currentArgs[i].key == name ) return _currentArgs[i].value; @@ -666,9 +666,9 @@ int WebServer::args() { bool WebServer::hasArg(String name) { for (int j = 0; j < _postArgsLen; ++j) { - if (_postArgs[j].key == name) - return true; - } + if (_postArgs[j].key == name) + return true; + } for (int i = 0; i < _currentArgCount; ++i) { if (_currentArgs[i].key == name) return true;