Web server simplifications and handers (#7429)

* First stab ad simplyfing webserver auth and adding a handler.

* Tweaks after testing against docs and latest Library tree

* Add documentatin for callback handler

* Bodge to allow things to compile without the dependencies

* Remove dependency on sodium to make it compile with 4.4

* Fix hex conversion

* 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.

* Remove some duplicated code

* Add simplfiied HEXBuilder under MD5Bulder to CMakefile.

* Update for 3.0.0 and QoL improvements

* Remove examples that depend on external libraries

* Skip H2 testing

* Formatting improvements

* Move builders examples to Utilities folder

* Fix indentation

* Add HashBuilder abstract class

* Add SHA1Builder

* Fix comment

* Fix whitespace

* Fix crashes and improve log messages

* Fix indentation for webserver

---------

Co-authored-by: Rodrigo Garcia <rodrigo.garcia@espressif.com>
Co-authored-by: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com>
This commit is contained in:
Dirk-Willem van Gulik 2024-01-16 14:49:46 +01:00 committed by GitHub
parent a114af068b
commit e581717bf3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 1417 additions and 146 deletions

View file

@ -50,12 +50,14 @@ 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/libb64/cdecode.c
cores/esp32/libb64/cencode.c
cores/esp32/main.cpp
cores/esp32/MD5Builder.cpp
cores/esp32/Print.cpp
cores/esp32/SHA1Builder.cpp
cores/esp32/stdlib_noniso.c
cores/esp32/Stream.cpp
cores/esp32/StreamString.cpp

View file

@ -0,0 +1,71 @@
/*
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
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
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 <Arduino.h>
#include <HEXBuilder.h>
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;
}

34
cores/esp32/HEXBuilder.h Normal file
View file

@ -0,0 +1,34 @@
/*
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
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
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 HEXBuilder_h
#define HEXBuilder_h
#include <WString.h>
#include <Stream.h>
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

60
cores/esp32/HashBuilder.h Normal file
View file

@ -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 <WString.h>
#include <Stream.h>
#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

View file

@ -16,15 +16,10 @@
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 <Arduino.h>
#include <MD5Builder.h>
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;
}
#include <Arduino.h>
#include <HEXBuilder.h>
#include <MD5Builder.h>
void MD5Builder::begin(void)
{
@ -32,23 +27,19 @@ 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 i, len = strlen(data);
size_t len = strlen(data);
uint8_t * tmp = (uint8_t*)malloc(len/2);
if(tmp == NULL) {
return;
}
for(i=0; i<len; i+=2) {
uint8_t high = hex_char_to_byte(data[i]);
uint8_t low = hex_char_to_byte(data[i+1]);
tmp[i/2] = (high & 0x0F) << 4 | (low & 0x0F);
}
hex2bytes(tmp, len/2, data);
add(tmp, len/2);
free(tmp);
}
@ -105,9 +96,7 @@ void MD5Builder::getBytes(uint8_t * output)
void MD5Builder::getChars(char * output)
{
for(uint8_t i = 0; i < ESP_ROM_MD5_DIGEST_LEN; i++) {
sprintf(output + (i * 2), "%02x", _buf[i]);
}
bytes2hex(output, ESP_ROM_MD5_DIGEST_LEN*2+1, _buf, ESP_ROM_MD5_DIGEST_LEN);
}
String MD5Builder::toString(void)

View file

@ -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 <WString.h>
#include <Stream.h>
@ -25,41 +27,27 @@
#include "esp_system.h"
#include "esp_rom_md5.h"
class MD5Builder
#include "HashBuilder.h"
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

367
cores/esp32/SHA1Builder.cpp Normal file
View file

@ -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 <Arduino.h>
#include <SHA1Builder.h>
// 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);
}

51
cores/esp32/SHA1Builder.h Normal file
View file

@ -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 <WString.h>
#include <Stream.h>
#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

View file

@ -0,0 +1,71 @@
#include <HEXBuilder.h>
void setup() {
Serial.begin(115200);
while (!Serial) { delay(10); }
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() {}

View file

@ -0,0 +1,96 @@
#include <MD5Builder.h>
// 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);
while (!Serial) { delay(10); }
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() {}

View file

@ -0,0 +1,97 @@
#include <SHA1Builder.h>
// 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() {}

View file

@ -0,0 +1,64 @@
#include <WiFi.h>
#include <ESPmDNS.h>
#include <ArduinoOTA.h>
#include <WebServer.h>
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);
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();
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
}

View file

@ -0,0 +1,67 @@
#include <WiFi.h>
#include <ESPmDNS.h>
#include <ArduinoOTA.h>
#include <WebServer.h>
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);
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();
server.on("/", []() {
if (!server.authenticate([](HTTPAuthMethod mode, String username, String params[]) -> String * {
// 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
}

View file

@ -0,0 +1,72 @@
#include <WiFi.h>
#include <ESPmDNS.h>
#include <ArduinoOTA.h>
#include <WebServer.h>
// 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);
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();
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
}

View file

@ -0,0 +1,117 @@
#include <WiFi.h>
#include <ESPmDNS.h>
#include <ArduinoOTA.h>
#include <WebServer.h>
#include <SHA1Builder.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 secret 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 = "admin";
const char* www_password_hex = "8cb124f8c277c16ec0b2ee00569fd151a08e342b";
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 "))) {
String secret = authReq.substring(7);
secret.trim();
uint8_t sha1[20];
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");
}
}
// No auth found
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();
// Convert token to a convenient binary representation.
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)) {
Serial.println("No/failed authentication");
return server.requestAuthentication();
}
Serial.println("Authentication succeeded");
server.send(200, "text/plain", "Login OK");
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
}

View file

@ -23,6 +23,7 @@
#include <Arduino.h>
#include <esp32-hal-log.h>
#include <libb64/cdecode.h>
#include <libb64/cencode.h>
#include "esp_random.h"
#include "WiFiServer.h"
@ -31,7 +32,8 @@
#include "FS.h"
#include "detail/RequestHandlersImpl.h"
#include "MD5Builder.h"
#include "SHA1Builder.h"
#include "base64.h"
static const char AUTHORIZATION_HEADER[] = "Authorization";
static const char qop_auth[] PROGMEM = "qop=auth";
@ -40,7 +42,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)
@ -128,91 +129,185 @@ 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=\""),'\"');
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.
if((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_response.length()) || (!_opaque.length())) {
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;
});
}
bool WebServer::authenticate(THandlerFunctionAuthCheck fn) {
if (!hasHeader(FPSTR(AUTHORIZATION_HEADER))) {
return false;
}
String authReq = header(FPSTR(AUTHORIZATION_HEADER));
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;
if (decoded) {
char* p;
if (base64_decode_chars(authReq.c_str(), authReq.length(), decoded) && (p = index(decoded, ':')) && p) {
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;
/* 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)) {
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=\""), '\"');
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());
bool ret = _response == _responsecheck;
log_v("Authentication %s", ret ? "Success" : "Failed");
return ret;
} else if (authReq.length()) {
// 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;
}
@ -232,11 +327,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 +506,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")));
@ -543,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;
@ -571,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;

View file

@ -34,7 +34,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
@ -46,6 +46,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)
@ -82,12 +83,41 @@ public:
virtual void close();
void stop();
const String AuthTypeDigest = F("Digest");
const String AuthTypeBasic = F("Basic");
/* 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<String * (HTTPAuthMethod mode, String enteredUsernameOrReq, String extraParams[])> 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<void(void)> 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 );