diff --git a/src/lib/ESPSigner/ESPSigner.cpp b/src/lib/ESPSigner/ESPSigner.cpp new file mode 100644 index 0000000..05bcc33 --- /dev/null +++ b/src/lib/ESPSigner/ESPSigner.cpp @@ -0,0 +1,1417 @@ +/** + * Google's OAuth2.0 Access token Generation class, Signer.h version 1.1.6 + * + * This library used RS256 for signing algorithm. + * + * The signed JWT token will be generated and exchanged with the access token in the final generating process. + * + * This library supports Espressif ESP8266 and ESP32 + * + * Created April 23, 2022 + * + * The MIT License (MIT) + * Copyright (c) 2022 K. Suwatchai (Mobizt) + * + * + * Permission is hereby granted, free of charge, to any person returning a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef ESP_SIGNER_CPP +#define ESP_SIGNER_CPP +#include "ESPSigner.h" + +ESP_Signer::ESP_Signer() +{ +} + +ESP_Signer::~ESP_Signer() +{ + if (ut) + delete ut; +} + +void ESP_Signer::begin(SignerConfig *cfg) +{ + if (ut) + delete ut; + config = cfg; + ut = new SignerUtils(config); + config->signer.tokens.error.message.clear(); + + config->internal.esp_signer_reconnect_wifi = WiFi.getAutoReconnect(); + + if (config->service_account.json.path.length() > 0) + { + if (!parseSAFile()) + config->signer.tokens.status = esp_signer_token_status_uninitialized; + } + + if (tokenSAReady()) + config->signer.tokens.token_type = esp_signer_token_type_oauth2_access_token; + + if (strlen_P(config->cert.data)) + config->internal.esp_signer_caCert = config->cert.data; + + if (config->cert.file.length() > 0) + { + if (config->cert.file_storage == esp_signer_mem_storage_type_sd && !config->internal.esp_signer_sd_rdy) + config->internal.esp_signer_sd_rdy = ut->sdTest(config->internal.esp_signer_file); + else if ((config->cert.file_storage == esp_signer_mem_storage_type_flash) && !config->internal.esp_signer_flash_rdy) + ut->flashTest(); + } + + _token_processing_task_end_request = false; + + handleToken(); +} + +void ESP_Signer::end() +{ + if (!config || _token_processing_task_end_request) + return; + config->signer.tokens.expires = 0; + _token_processing_task_end_request = true; +} + +bool ESP_Signer::parseSAFile() +{ + if (config->signer.pk.length() > 0) + return false; + + if (config->service_account.json.storage_type == esp_signer_mem_storage_type_sd && !config->internal.esp_signer_sd_rdy) + config->internal.esp_signer_sd_rdy = ut->sdTest(config->internal.esp_signer_file); + else if (config->service_account.json.storage_type == esp_signer_mem_storage_type_flash && !config->internal.esp_signer_flash_rdy) + ut->flashTest(); + + if (config->internal.esp_signer_sd_rdy || config->internal.esp_signer_flash_rdy) + { + if (config->service_account.json.storage_type == esp_signer_mem_storage_type_flash) + { + if (FLASH_FS.exists(config->service_account.json.path.c_str())) + config->internal.esp_signer_file = FLASH_FS.open(config->service_account.json.path.c_str(), "r"); + } + else + { + if (SD_FS.exists(config->service_account.json.path.c_str())) + config->internal.esp_signer_file = SD_FS.open(config->service_account.json.path.c_str(), "r"); + } + + if (config->internal.esp_signer_file) + { + clearSA(); + config->signer.json = new FirebaseJson(); + config->signer.result = new FirebaseJsonData(); + char *tmp = nullptr; + + size_t len = config->internal.esp_signer_file.size(); + char *buf = (char *)ut->newP(len + 10); + if (config->internal.esp_signer_file.available()) + { + config->internal.esp_signer_file.readBytes(buf, len); + config->signer.json->setJsonData(buf); + } + config->internal.esp_signer_file.close(); + ut->delP(&buf); + + if (parseJsonResponse(esp_signer_pgm_str_13)) + { + if (ut->strposP(config->signer.result->to(), esp_signer_pgm_str_14, 0) > -1) + { + if (parseJsonResponse(esp_signer_pgm_str_15)) + config->service_account.data.project_id = config->signer.result->to(); + + if (parseJsonResponse(esp_signer_pgm_str_16)) + config->service_account.data.private_key_id = config->signer.result->to(); + + if (parseJsonResponse(esp_signer_pgm_str_17)) + { + size_t len = strlen(config->signer.result->to()); + tmp = (char *)ut->newP(len); + size_t c = 0; + for (size_t i = 0; i < len; i++) + { + if (config->signer.result->to()[i] == '\\') + { + ut->idle(); + tmp[c++] = '\n'; + i++; + } + else + tmp[c++] = config->signer.result->to()[i]; + } + config->signer.pk = tmp; + config->signer.result->clear(); + ut->delP(&tmp); + } + + if (parseJsonResponse(esp_signer_pgm_str_18)) + config->service_account.data.client_email = config->signer.result->to(); + + if (parseJsonResponse(esp_signer_pgm_str_19)) + config->service_account.data.client_id = config->signer.result->to(); + + delete config->signer.json; + delete config->signer.result; + return true; + } + } + + delete config->signer.json; + delete config->signer.result; + } + } + + return false; +} + +void ESP_Signer::clearSA() +{ + config->service_account.data.private_key = ""; + config->service_account.data.project_id.clear(); + config->service_account.data.private_key_id.clear(); + config->service_account.data.client_email.clear(); + config->signer.pk.clear(); +} + +bool ESP_Signer::tokenSAReady() +{ + if (!config) + return false; + return (strlen_P(config->service_account.data.private_key) > 0 || config->signer.pk.length() > 0) && config->service_account.data.client_email.length() > 0 && config->service_account.data.project_id.length() > 0; +} + +bool ESP_Signer::handleToken() +{ + if (!config) + return false; + + if (config->signer.tokens.token_type == esp_signer_token_type_oauth2_access_token && isExpired()) + { + + if (config->signer.step == esp_signer_jwt_generation_step_begin) + { + + if (!config->signer.tokenTaskRunning) + { + if (config->service_account.json.path.length() > 0 && config->signer.pk.length() == 0) + { + if (!parseSAFile()) + config->signer.tokens.status = esp_signer_token_status_uninitialized; + } + + if (config->signer.tokens.status != esp_signer_token_status_on_initialize) + { + + config->signer.tokens.status = esp_signer_token_status_on_initialize; + config->signer.tokens.error.code = 0; + config->signer.tokens.error.message.clear(); + config->internal.esp_signer_last_jwt_generation_error_cb_millis = 0; + sendTokenStatusCB(); + } + + _token_processing_task_enable = true; + tokenProcessingTask(); + } + } + } + + if (config->signer.tokens.token_type == esp_signer_token_type_undefined) + setTokenError(ESP_SIGNER_ERROR_TOKEN_NOT_READY); + + return config->signer.tokens.status == esp_signer_token_status_ready; +} + +bool ESP_Signer::isExpired() +{ + if (!config) + return false; + + // if the time was set (changed) after token has been generated, update its expiration + if (config->signer.tokens.expires > 0 && config->signer.tokens.expires < ESP_DEFAULT_TS && time(nullptr) > ESP_DEFAULT_TS) + config->signer.tokens.expires += time(nullptr) - (millis() - config->signer.tokens.last_millis) / 1000 - 60; + + if (config->signer.preRefreshSeconds > config->signer.tokens.expires && config->signer.tokens.expires > 0) + config->signer.preRefreshSeconds = 60; + + return ((unsigned long)time(nullptr) > config->signer.tokens.expires - config->signer.preRefreshSeconds || config->signer.tokens.expires == 0); +} + +void ESP_Signer::tokenProcessingTask() +{ + + if (config->signer.tokenTaskRunning) + return; + + bool ret = false; + + config->signer.tokenTaskRunning = true; + + bool sslValidTime = false; + +#if defined(ESP8266) + if (config->cert.data != NULL || config->cert.file.length() > 0) + sslValidTime = true; +#endif + + while (!ret && config->signer.tokens.status != esp_signer_token_status_ready) + { + delay(0); + + // check time if clock synching once set + if (!config->internal.esp_signer_clock_rdy && (config->internal.esp_signer_clock_synched || sslValidTime)) + { + if (millis() - config->internal.esp_signer_last_time_sync_millis > 1500) + { + config->internal.esp_signer_last_time_sync_millis = millis(); + + if (millis() - config->internal.esp_signer_last_ntp_sync_timeout_millis > config->timeout.ntpServerRequest) + { + config->internal.esp_signer_last_ntp_sync_timeout_millis = millis(); + config->signer.tokens.error.message.clear(); + setTokenError(ESP_SIGNER_ERROR_NTP_SYNC_TIMED_OUT); + sendTokenStatusCB(); + config->signer.tokens.status = esp_signer_token_status_on_initialize; + config->internal.esp_signer_last_jwt_generation_error_cb_millis = 0; + } + + // set clock again if timed out + config->internal.esp_signer_clock_synched = false; + } + + // check or set time again + ut->syncClock(config->time_zone); + + if (!config->internal.esp_signer_clock_rdy) + { + config->signer.tokenTaskRunning = false; + return; + } + } + + if (config->signer.step == esp_signer_jwt_generation_step_begin && (millis() - config->internal.esp_signer_last_jwt_begin_step_millis > config->timeout.tokenGenerationBeginStep || config->internal.esp_signer_last_jwt_begin_step_millis == 0)) + { + config->internal.esp_signer_last_jwt_begin_step_millis = millis(); + ut->syncClock(config->time_zone); + + if (config->internal.esp_signer_clock_rdy) + config->signer.step = esp_signer_jwt_generation_step_encode_header_payload; + } + else if (config->signer.step == esp_signer_jwt_generation_step_encode_header_payload) + { + if (createJWT()) + config->signer.step = esp_signer_jwt_generation_step_sign; + } + else if (config->signer.step == esp_signer_jwt_generation_step_sign) + { + if (createJWT()) + config->signer.step = esp_signer_jwt_generation_step_exchange; + } + else if (config->signer.step == esp_signer_jwt_generation_step_exchange) + { + + requestTokens(); + + _token_processing_task_enable = false; + config->signer.step = esp_signer_jwt_generation_step_begin; + ret = true; + } + } + + config->signer.tokenTaskRunning = false; +} + +void ESP_Signer::setTokenError(int code) +{ + if (code != 0) + config->signer.tokens.status = esp_signer_token_status_error; + else + { + config->signer.tokens.error.message.clear(); + config->signer.tokens.status = esp_signer_token_status_ready; + } + + config->signer.tokens.error.code = code; + + if (config->signer.tokens.error.message.length() == 0) + { + config->internal.esp_signer_processing = false; + switch (code) + { + case ESP_SIGNER_ERROR_TOKEN_SET_TIME: + config->signer.tokens.error.message = esp_signer_pgm_str_21; + break; + case ESP_SIGNER_ERROR_TOKEN_PARSE_PK: + config->signer.tokens.error.message = esp_signer_pgm_str_22; + break; + case ESP_SIGNER_ERROR_TOKEN_CREATE_HASH: + config->signer.tokens.error.message = esp_signer_pgm_str_23; + break; + case ESP_SIGNER_ERROR_TOKEN_SIGN: + config->signer.tokens.error.message = esp_signer_pgm_str_24; + break; + case ESP_SIGNER_ERROR_TOKEN_EXCHANGE: + config->signer.tokens.error.message = esp_signer_pgm_str_25; + break; + case ESP_SIGNER_ERROR_TOKEN_NOT_READY: + config->signer.tokens.error.message = esp_signer_pgm_str_26; + break; + case ESP_SIGNER_ERROR_TOKEN_EXCHANGE_MAX_RETRY_REACHED: + config->signer.tokens.error.message = esp_signer_pgm_str_27; + break; + case ESP_SIGNER_ERROR_TCP_ERROR_NOT_CONNECTED: + config->signer.tokens.error.message += esp_signer_pgm_str_28; + break; + case ESP_SIGNER_ERROR_TCP_ERROR_CONNECTION_LOST: + config->signer.tokens.error.message += esp_signer_pgm_str_29; + break; + case ESP_SIGNER_ERROR_HTTP_CODE_REQUEST_TIMEOUT: + config->signer.tokens.error.message += esp_signer_pgm_str_30; + break; + case ESP_SIGNER_ERROR_NTP_SYNC_TIMED_OUT: + config->signer.tokens.error.message += esp_signer_pgm_str_116; + break; + + default: + break; + } + } +} + +bool ESP_Signer::sdBegin(int8_t ss, int8_t sck, int8_t miso, int8_t mosi) +{ + if (config) + { + config->internal.sd_config.sck = sck; + config->internal.sd_config.miso = miso; + config->internal.sd_config.mosi = mosi; + config->internal.sd_config.ss = ss; + } +#if defined(ESP32) + if (ss > -1) + { + SPI.begin(sck, miso, mosi, ss); + return SD_FS.begin(ss, SPI); + } + else + return SD_FS.begin(); +#elif defined(ESP8266) + if (ss > -1) + return SD_FS.begin(ss); + else + return SD_FS.begin(SD_CS_PIN); +#endif + return false; +} + +bool ESP_Signer::sdMMCBegin(const char *mountpoint, bool mode1bit, bool format_if_mount_failed) +{ +#if defined(ESP32) +#if defined(CARD_TYPE_SD_MMC) + if (config) + { + config->internal.sd_config.sd_mmc_mountpoint = mountpoint; + config->internal.sd_config.sd_mmc_mode1bit = mode1bit; + config->internal.sd_config.sd_mmc_format_if_mount_failed = format_if_mount_failed; + } + return SD_FS.begin(mountpoint, mode1bit, format_if_mount_failed); +#endif +#endif + return false; +} + +bool ESP_Signer::handleSignerError(int code, int httpCode) +{ + + switch (code) + { + + case 1: + config->signer.tokens.error.message.clear(); + setTokenError(ESP_SIGNER_ERROR_TCP_ERROR_NOT_CONNECTED); + config->internal.esp_signer_last_jwt_generation_error_cb_millis = 0; + sendTokenStatusCB(); + break; + case 2: + + if (config->signer.wcs->stream()) + config->signer.wcs->stream()->stop(); + + config->signer.tokens.error.message.clear(); + setTokenError(ESP_SIGNER_ERROR_TCP_ERROR_CONNECTION_LOST); + config->internal.esp_signer_last_jwt_generation_error_cb_millis = 0; + sendTokenStatusCB(); + break; + case 3: + + if (config->signer.wcs->stream()) + config->signer.wcs->stream()->stop(); + + if (httpCode == 0) + { + setTokenError(ESP_SIGNER_ERROR_HTTP_CODE_REQUEST_TIMEOUT); + config->signer.tokens.error.message.clear(); + } + else + { + errorToString(httpCode, config->signer.tokens.error.message); + setTokenError(httpCode); + } + config->internal.esp_signer_last_jwt_generation_error_cb_millis = 0; + sendTokenStatusCB(); + + break; + + default: + break; + } + + if (config->signer.wcs) + delete config->signer.wcs; + if (config->signer.json) + delete config->signer.json; + if (config->signer.result) + delete config->signer.result; + + config->signer.wcs = NULL; + config->signer.json = NULL; + config->signer.result = NULL; + + config->internal.esp_signer_processing = false; + + if (code > 0 && code < 4) + { + config->signer.tokens.status = esp_signer_token_status_error; + config->signer.tokens.error.code = code; + return false; + } + else if (code <= 0) + { + config->signer.tokens.error.message.clear(); + config->signer.tokens.status = esp_signer_token_status_ready; + config->signer.step = esp_signer_jwt_generation_step_begin; + config->internal.esp_signer_last_jwt_generation_error_cb_millis = 0; + if (code == 0) + sendTokenStatusCB(); + return true; + } + + return false; +} + +void ESP_Signer::sendTokenStatusCB() +{ + tokenInfo.status = config->signer.tokens.status; + tokenInfo.type = config->signer.tokens.token_type; + tokenInfo.error = config->signer.tokens.error; + + if (config->token_status_callback) + { + if (millis() - config->internal.esp_signer_last_jwt_generation_error_cb_millis > config->timeout.tokenGenerationError || config->internal.esp_signer_last_jwt_generation_error_cb_millis == 0) + { + config->internal.esp_signer_last_jwt_generation_error_cb_millis = millis(); + config->token_status_callback(tokenInfo); + } + } +} + +bool ESP_Signer::parseJsonResponse(PGM_P key_path) +{ + char *tmp = ut->strP(key_path); + config->signer.result->clear(); + config->signer.json->get(*config->signer.result, tmp); + ut->delP(&tmp); + return config->signer.result->success; +} + +bool ESP_Signer::handleTokenResponse(int &httpCode) +{ + if (config->internal.esp_signer_reconnect_wifi) + ut->reconnect(0); + + if (WiFi.status() != WL_CONNECTED && !ut->ethLinkUp(&config->spi_ethernet_module)) + return false; + + struct esp_signer_server_response_data_t response; + + unsigned long dataTime = millis(); + + int chunkIdx = 0; + int chunkBufSize = 0; + int chunkedDataState = 0; + int chunkedDataSize = 0; + int chunkedDataLen = 0; + MB_String header, payload; + bool isHeader = false; + WiFiClient *stream = config->signer.wcs->stream(); + while (stream->connected() && stream->available() == 0) + { + if (!ut->reconnect(dataTime)) + { + if (stream) + if (stream->connected()) + stream->stop(); + return false; + } + + ut->idle(); + } + + bool complete = false; + unsigned long datatime = millis(); + while (!complete) + { + + chunkBufSize = stream->available(); + + if (chunkBufSize > 1 || !complete) + { + while (!complete) + { + ut->idle(); + + if (config->internal.esp_signer_reconnect_wifi) + ut->reconnect(0); + + if (WiFi.status() != WL_CONNECTED && !ut->ethLinkUp(&config->spi_ethernet_module)) + { + if (stream) + if (stream->connected()) + stream->stop(); + return false; + } + chunkBufSize = stream->available(); + + if (chunkBufSize > 0) + { + if (chunkIdx == 0) + { + ut->readLine(stream, header); + int pos = 0; + char *tmp = ut->getHeader(header.c_str(), esp_signer_pgm_str_31, esp_signer_pgm_str_32, pos, 0); + if (tmp) + { + isHeader = true; + response.httpCode = atoi(tmp); + ut->delP(&tmp); + } + } + else + { + if (isHeader) + { + char *tmp = (char *)ut->newP(chunkBufSize); + int readLen = ut->readLine(stream, tmp, chunkBufSize); + bool headerEnded = false; + + if (readLen == 1) + if (tmp[0] == '\r') + headerEnded = true; + + if (readLen == 2) + if (tmp[0] == '\r' && tmp[1] == '\n') + headerEnded = true; + + if (headerEnded) + { + isHeader = false; + ut->parseRespHeader(header.c_str(), response); + header.clear(); + } + else + header += tmp; + + ut->delP(&tmp); + } + else + { + if (!response.noContent) + { + if (response.isChunkedEnc) + complete = ut->readChunkedData(stream, payload, chunkedDataState, chunkedDataSize, chunkedDataLen) < 0; + else + { + chunkBufSize = 1024; + if (stream->available() < chunkBufSize) + chunkBufSize = stream->available(); + + char *tmp = (char *)ut->newP(chunkBufSize + 1); + int readLen = stream->readBytes(tmp, chunkBufSize); + + if (readLen > 0) + payload += tmp; + + ut->delP(&tmp); + complete = stream->available() <= 0; + } + } + else + { + while (stream->available() > 0) + stream->read(); + if (stream->available() <= 0) + break; + } + } + } + chunkIdx++; + } + + if (millis() - datatime > 5000) + complete = true; + } + } + } + + if (!config->signer.reuseSession && stream->connected()) + stream->stop(); + + httpCode = response.httpCode; + + if (payload.length() > 0 && !response.noContent) + { + + config->signer.json->setJsonData(payload.c_str()); + payload.clear(); + return true; + } + + return false; +} + +bool ESP_Signer::createJWT() +{ + + if (config->signer.step == esp_signer_jwt_generation_step_encode_header_payload) + { + config->signer.tokens.status = esp_signer_token_status_on_signing; + config->signer.tokens.error.code = 0; + config->signer.tokens.error.message.clear(); + config->internal.esp_signer_last_jwt_generation_error_cb_millis = 0; + sendTokenStatusCB(); + + config->signer.json = new FirebaseJson(); + config->signer.result = new FirebaseJsonData(); + + unsigned long now = time(nullptr); + + config->signer.tokens.jwt.clear(); + + // header + char *tmp = ut->strP(esp_signer_pgm_str_33); + char *tmp2 = ut->strP(esp_signer_pgm_str_34); + config->signer.json->add(tmp, (const char *)tmp2); + ut->delP(&tmp); + ut->delP(&tmp2); + tmp2 = ut->strP(esp_signer_pgm_str_35); + tmp = ut->strP(esp_signer_pgm_str_36); + config->signer.json->add(tmp, (const char *)tmp2); + ut->delP(&tmp); + ut->delP(&tmp2); + + MB_String hdr; + config->signer.json->toString(hdr); + size_t len = ut->base64EncLen(hdr.length()); + char *buf = (char *)ut->newP(len); + ut->encodeBase64Url(buf, (unsigned char *)hdr.c_str(), hdr.length()); + config->signer.encHeader = buf; + ut->delP(&buf); + config->signer.encHeadPayload = config->signer.encHeader; + hdr.clear(); + + // payload + config->signer.json->clear(); + tmp = ut->strP(esp_signer_pgm_str_37); + config->signer.json->add(tmp, config->service_account.data.client_email.c_str()); + ut->delP(&tmp); + tmp = ut->strP(esp_signer_pgm_str_38); + config->signer.json->add(tmp, config->service_account.data.client_email.c_str()); + ut->delP(&tmp); + tmp = ut->strP(esp_signer_pgm_str_39); + MB_String t = esp_signer_pgm_str_40; + if (config->signer.tokens.token_type == esp_signer_token_type_oauth2_access_token) + { + t += esp_signer_pgm_str_41; + t += esp_signer_pgm_str_42; + t += esp_signer_pgm_str_43; + t += esp_signer_pgm_str_44; + t += esp_signer_pgm_str_45; + } + + config->signer.json->add(tmp, t.c_str()); + ut->delP(&tmp); + + tmp = ut->strP(esp_signer_pgm_str_46); + config->signer.json->add(tmp, (int)now); + ut->delP(&tmp); + + tmp = ut->strP(esp_signer_pgm_str_47); + + if (config->signer.expiredSeconds > 3600) + config->signer.json->add(tmp, (int)(now + 3600)); + else + config->signer.json->add(tmp, (int)(now + config->signer.expiredSeconds)); + + ut->delP(&tmp); + + if (config->signer.tokens.token_type == esp_signer_token_type_oauth2_access_token) + { + + MB_String s; + + if (config->signer.tokens.scope.length() > 0) + { + std::vector scopes = std::vector(); + ut->splitTk(config->signer.tokens.scope, scopes, ","); + for (size_t i = 0; i < scopes.size(); i++) + { + if (s.length() > 0) + s += esp_signer_pgm_str_32; + s += scopes[i]; + scopes[i].clear(); + } + scopes.clear(); + } + + tmp = ut->strP(esp_signer_pgm_str_56); + config->signer.json->add(tmp, s.c_str()); + ut->delP(&tmp); + } + + MB_String payload; + config->signer.json->toString(payload); + + len = ut->base64EncLen(payload.length()); + buf = (char *)ut->newP(len); + ut->encodeBase64Url(buf, (unsigned char *)payload.c_str(), payload.length()); + config->signer.encPayload = buf; + ut->delP(&buf); + payload.clear(); + + config->signer.encHeadPayload += esp_signer_pgm_str_42; + config->signer.encHeadPayload += config->signer.encPayload; + + config->signer.encHeader.clear(); + config->signer.encPayload.clear(); + +// create message digest from encoded header and payload +#if defined(ESP32) + config->signer.hash = (uint8_t *)ut->newP(config->signer.hashSize); + int ret = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), (const unsigned char *)config->signer.encHeadPayload.c_str(), config->signer.encHeadPayload.length(), config->signer.hash); + if (ret != 0) + { + char *tmp = (char *)ut->newP(100); + mbedtls_strerror(ret, tmp, 100); + config->signer.tokens.error.message = tmp; + config->signer.tokens.error.message.insert(0, (const char *)FPSTR("mbedTLS, mbedtls_md: ")); + ut->delP(&tmp); + setTokenError(ESP_SIGNER_ERROR_TOKEN_CREATE_HASH); + sendTokenStatusCB(); + ut->delP(&config->signer.hash); + return false; + } +#elif defined(ESP8266) + config->signer.hash = (char *)ut->newP(config->signer.hashSize); + br_sha256_context mc; + br_sha256_init(&mc); + br_sha256_update(&mc, config->signer.encHeadPayload.c_str(), config->signer.encHeadPayload.length()); + br_sha256_out(&mc, config->signer.hash); +#endif + + config->signer.tokens.jwt = config->signer.encHeadPayload; + config->signer.tokens.jwt += esp_signer_pgm_str_42; + config->signer.encHeadPayload.clear(); + + delete config->signer.json; + delete config->signer.result; + } + else if (config->signer.step == esp_signer_jwt_generation_step_sign) + { + config->signer.tokens.status = esp_signer_token_status_on_signing; + +#if defined(ESP32) + config->signer.pk_ctx = new mbedtls_pk_context(); + mbedtls_pk_init(config->signer.pk_ctx); + + // parse priv key + int ret = 0; + if (config->signer.pk.length() > 0) + ret = mbedtls_pk_parse_key(config->signer.pk_ctx, (const unsigned char *)config->signer.pk.c_str(), config->signer.pk.length() + 1, NULL, 0); + else if (strlen_P(config->service_account.data.private_key) > 0) + ret = mbedtls_pk_parse_key(config->signer.pk_ctx, (const unsigned char *)config->service_account.data.private_key, strlen_P(config->service_account.data.private_key) + 1, NULL, 0); + + if (ret != 0) + { + char *tmp = (char *)ut->newP(100); + mbedtls_strerror(ret, tmp, 100); + config->signer.tokens.error.message = tmp; + config->signer.tokens.error.message.insert(0, (const char *)FPSTR("mbedTLS, mbedtls_pk_parse_key: ")); + ut->delP(&tmp); + setTokenError(ESP_SIGNER_ERROR_TOKEN_PARSE_PK); + sendTokenStatusCB(); + mbedtls_pk_free(config->signer.pk_ctx); + ut->delP(&config->signer.hash); + delete config->signer.pk_ctx; + return false; + } + + // generate RSA signature from private key and message digest + config->signer.signature = (unsigned char *)ut->newP(config->signer.signatureSize); + size_t sigLen = 0; + config->signer.entropy_ctx = new mbedtls_entropy_context(); + config->signer.ctr_drbg_ctx = new mbedtls_ctr_drbg_context(); + mbedtls_entropy_init(config->signer.entropy_ctx); + mbedtls_ctr_drbg_init(config->signer.ctr_drbg_ctx); + mbedtls_ctr_drbg_seed(config->signer.ctr_drbg_ctx, mbedtls_entropy_func, config->signer.entropy_ctx, NULL, 0); + + ret = mbedtls_pk_sign(config->signer.pk_ctx, MBEDTLS_MD_SHA256, (const unsigned char *)config->signer.hash, config->signer.hashSize, config->signer.signature, &sigLen, mbedtls_ctr_drbg_random, config->signer.ctr_drbg_ctx); + if (ret != 0) + { + char *tmp = (char *)ut->newP(100); + mbedtls_strerror(ret, tmp, 100); + config->signer.tokens.error.message = tmp; + config->signer.tokens.error.message.insert(0, (const char *)FPSTR("mbedTLS, mbedtls_pk_sign: ")); + ut->delP(&tmp); + setTokenError(ESP_SIGNER_ERROR_TOKEN_SIGN); + sendTokenStatusCB(); + } + else + { + config->signer.encSignature.clear(); + size_t len = ut->base64EncLen(config->signer.signatureSize); + char *buf = (char *)ut->newP(len); + ut->encodeBase64Url(buf, config->signer.signature, config->signer.signatureSize); + config->signer.encSignature = buf; + ut->delP(&buf); + + config->signer.tokens.jwt += config->signer.encSignature; + config->signer.pk.clear(); + config->signer.encSignature.clear(); + } + + ut->delP(&config->signer.signature); + ut->delP(&config->signer.hash); + mbedtls_pk_free(config->signer.pk_ctx); + mbedtls_entropy_free(config->signer.entropy_ctx); + mbedtls_ctr_drbg_free(config->signer.ctr_drbg_ctx); + delete config->signer.pk_ctx; + delete config->signer.entropy_ctx; + delete config->signer.ctr_drbg_ctx; + + if (ret != 0) + return false; +#elif defined(ESP8266) + // RSA private key + BearSSL::PrivateKey *pk = nullptr; + ut->idle(); + // parse priv key + if (config->signer.pk.length() > 0) + pk = new BearSSL::PrivateKey((const char *)config->signer.pk.c_str()); + else if (strlen_P(config->service_account.data.private_key) > 0) + pk = new BearSSL::PrivateKey((const char *)config->service_account.data.private_key); + + if (!pk) + { + setTokenError(ESP_SIGNER_ERROR_TOKEN_PARSE_PK); + config->signer.tokens.error.message.insert(0, (const char *)FPSTR("BearSSL, PrivateKey: ")); + sendTokenStatusCB(); + return false; + } + + if (!pk->isRSA()) + { + setTokenError(ESP_SIGNER_ERROR_TOKEN_PARSE_PK); + config->signer.tokens.error.message.insert(0, (const char *)FPSTR("BearSSL, isRSA: ")); + sendTokenStatusCB(); + delete pk; + return false; + } + + const br_rsa_private_key *br_rsa_key = pk->getRSA(); + + // generate RSA signature from private key and message digest + config->signer.signature = new unsigned char[config->signer.signatureSize]; + + ut->idle(); + int ret = br_rsa_i15_pkcs1_sign(BR_HASH_OID_SHA256, (const unsigned char *)config->signer.hash, br_sha256_SIZE, br_rsa_key, config->signer.signature); + ut->idle(); + ut->delP(&config->signer.hash); + + size_t len = ut->base64EncLen(config->signer.signatureSize); + char *buf = (char *)ut->newP(len); + ut->encodeBase64Url(buf, config->signer.signature, config->signer.signatureSize); + config->signer.encSignature = buf; + ut->delP(&buf); + ut->delP(&config->signer.signature); + delete pk; + // get the signed JWT + if (ret > 0) + { + config->signer.tokens.jwt += config->signer.encSignature; + config->signer.pk.clear(); + config->signer.encSignature.clear(); + } + else + { + setTokenError(ESP_SIGNER_ERROR_TOKEN_SIGN); + config->signer.tokens.error.message.insert(0, (const char *)FPSTR("BearSSL, br_rsa_i15_pkcs1_sign: ")); + sendTokenStatusCB(); + return false; + } +#endif + } + + return true; +} + +bool ESP_Signer::requestTokens() +{ + + if (config->internal.esp_signer_reconnect_wifi) + ut->reconnect(0); + + if (WiFi.status() != WL_CONNECTED && !ut->ethLinkUp(&config->spi_ethernet_module)) + return false; + + ut->idle(); + + if (config->signer.tokens.status == esp_signer_token_status_on_request || config->signer.tokens.status == esp_signer_token_status_on_refresh || time(nullptr) < ESP_DEFAULT_TS || config->internal.esp_signer_processing) + return false; + + config->signer.tokens.status = esp_signer_token_status_on_request; + config->internal.esp_signer_processing = true; + config->signer.tokens.error.code = 0; + config->signer.tokens.error.message.clear(); + config->internal.esp_signer_last_jwt_generation_error_cb_millis = 0; + sendTokenStatusCB(); + + config->signer.wcs = new ESP_SIGNER_TCP_Client(); + +#if defined(ESP32) + config->signer.wcs->setCACert(nullptr); +#elif defined(ESP8266) + config->signer.wcs->setBufferSizes(1024, 1024); +#endif + + config->signer.json = new FirebaseJson(); + config->signer.result = new FirebaseJsonData(); + + MB_String host = esp_signer_pgm_str_48; + host += esp_signer_pgm_str_42; + host += esp_signer_pgm_str_43; + + ut->idle(); + + config->signer.wcs->begin(host.c_str(), 443); +#if defined(ESP8266) + ut->ethDNSWorkAround(&ut->config->spi_ethernet_module, host.c_str(), 443); +#endif + + MB_String req = esp_signer_pgm_str_57; + req += esp_signer_pgm_str_32; + + if (config->signer.tokens.token_type == esp_signer_token_type_oauth2_access_token) + { + char *tmp = ut->strP(esp_signer_pgm_str_58); + char *tmp2 = ut->strP(esp_signer_pgm_str_59); + config->signer.json->add(tmp, (const char *)tmp2); + ut->delP(&tmp); + ut->delP(&tmp2); + tmp = ut->strP(esp_signer_pgm_str_60); + config->signer.json->add(tmp, config->signer.tokens.jwt.c_str()); + ut->delP(&tmp); + + req += esp_signer_pgm_str_44; + req += esp_signer_pgm_str_45; + req += esp_signer_pgm_str_61; + req += esp_signer_pgm_str_62; + req += esp_signer_pgm_str_41; + } + + req += esp_signer_pgm_str_42; + req += esp_signer_pgm_str_43; + + req += esp_signer_pgm_str_4; + req += esp_signer_pgm_str_64; + req += esp_signer_pgm_str_65; + req += strlen(config->signer.json->raw()); + req += esp_signer_pgm_str_4; + req += esp_signer_pgm_str_66; + req += esp_signer_pgm_str_67; + req += esp_signer_pgm_str_4; + req += esp_signer_pgm_str_4; + + req += config->signer.json->raw(); + + config->signer.wcs->setInsecure(); + int ret = config->signer.wcs->send(req.c_str()); + req.clear(); + if (ret < 0) + return handleSignerError(2); + + struct esp_signer_auth_token_error_t error; + + int httpCode = 0; + if (handleTokenResponse(httpCode)) + { + config->signer.tokens.jwt.clear(); + if (parseJsonResponse(esp_signer_pgm_str_68)) + { + + error.code = config->signer.result->to(); + config->signer.tokens.status = esp_signer_token_status_error; + + if (parseJsonResponse(esp_signer_pgm_str_69)) + error.message = config->signer.result->to(); + } + else if (parseJsonResponse(esp_signer_pgm_str_113)) + { + + error.code = -1; + config->signer.tokens.status = esp_signer_token_status_error; + + if (parseJsonResponse(esp_signer_pgm_str_12)) + error.message = config->signer.result->to(); + } + + if (error.code != 0 && config->signer.tokens.token_type == esp_signer_token_type_oauth2_access_token) + { + // new jwt needed as it is already cleared + config->signer.step = esp_signer_jwt_generation_step_encode_header_payload; + } + + config->signer.tokens.error = error; + tokenInfo.status = config->signer.tokens.status; + tokenInfo.type = config->signer.tokens.token_type; + tokenInfo.error = config->signer.tokens.error; + config->internal.esp_signer_last_jwt_generation_error_cb_millis = 0; + if (error.code != 0) + sendTokenStatusCB(); + + if (error.code == 0) + { + if (config->signer.tokens.token_type == esp_signer_token_type_oauth2_access_token) + { + if (parseJsonResponse(esp_signer_pgm_str_70)) + config->signer.tokens.access_token = config->signer.result->to(); + + if (parseJsonResponse(esp_signer_pgm_str_71)) + config->signer.tokens.auth_type = config->signer.result->to(); + + if (parseJsonResponse(esp_signer_pgm_str_72)) + getExpiration(config->signer.result->to()); + } + return handleSignerError(0); + } + return handleSignerError(4); + } + + return handleSignerError(3, httpCode); +} + +void ESP_Signer::getExpiration(const char *exp) +{ + time_t ts = time(nullptr); + unsigned long ms = millis(); + config->signer.tokens.expires = ts + atoi(exp); + config->signer.tokens.last_millis = ms; +} + +void ESP_Signer::checkToken() +{ + if (!config) + return; + + // if the time was set (changed) after token has been generated, update its expiration + if (config->signer.tokens.expires > 0 && config->signer.tokens.expires < ESP_DEFAULT_TS && time(nullptr) > ESP_DEFAULT_TS) + config->signer.tokens.expires += time(nullptr) - (millis() - config->signer.tokens.last_millis) / 1000 - 60; + + if (config->signer.preRefreshSeconds > config->signer.tokens.expires && config->signer.tokens.expires > 0) + config->signer.preRefreshSeconds = 60; + + if (_token_processing_task_end_request && config->signer.tokens.status == esp_signer_token_status_ready) + { + + config->signer.pk.clear(); + config->signer.tokens.jwt.clear(); + config->signer.tokens.access_token.clear(); + config->signer.tokens.token_type = esp_signer_token_type_undefined; + + config->signer.tokens.status = esp_signer_token_status_uninitialized; + config->signer.tokens.error.code = 0; + config->signer.tokens.error.message.clear(); + config->internal.esp_signer_last_jwt_generation_error_cb_millis = 0; + _token_processing_task_end_request = false; + _token_processing_task_enable = false; + } + + if (config->signer.tokens.token_type == esp_signer_token_type_oauth2_access_token && ((unsigned long)time(nullptr) > config->signer.tokens.expires - config->signer.preRefreshSeconds || config->signer.tokens.expires == 0)) + handleToken(); +} + +bool ESP_Signer::tokenReady() +{ + if (!config) + return false; + + checkToken(); + return config->signer.tokens.status == esp_signer_token_status_ready; +}; + +String ESP_Signer::accessToken() +{ + if (!config) + return ""; + return config->signer.tokens.access_token.c_str(); +} + +String ESP_Signer::getTokenType(TokenInfo info) +{ + if (!config) + return ""; + + MB_String s; + switch (info.type) + { + case esp_signer_token_type_undefined: + s = esp_signer_pgm_str_49; + break; + case esp_signer_token_type_oauth2_access_token: + s = esp_signer_pgm_str_50; + break; + default: + break; + } + return s.c_str(); +} + +String ESP_Signer::getTokenType() +{ + return getTokenType(tokenInfo); +} + +String ESP_Signer::getTokenStatus(TokenInfo info) +{ + if (!config) + return ""; + + MB_String s; + switch (info.status) + { + case esp_signer_token_status_uninitialized: + s = esp_signer_pgm_str_51; + break; + + case esp_signer_token_status_on_initialize: + s = esp_signer_pgm_str_52; + break; + case esp_signer_token_status_on_signing: + s = esp_signer_pgm_str_53; + break; + case esp_signer_token_status_on_request: + s = esp_signer_pgm_str_54; + break; + case esp_signer_token_status_on_refresh: + s = esp_signer_pgm_str_55; + break; + case esp_signer_token_status_ready: + s = esp_signer_pgm_str_112; + break; + case esp_signer_token_status_error: + s = esp_signer_pgm_str_113; + break; + default: + break; + } + return s.c_str(); +} + +String ESP_Signer::getTokenStatus() +{ + return getTokenStatus(tokenInfo); +} + +String ESP_Signer::getTokenError(TokenInfo info) +{ + if (!config) + return ""; + + MB_String s = esp_signer_pgm_str_114; + s += info.error.code; + s += esp_signer_pgm_str_115; + s += info.error.message; + return s.c_str(); +} + +String ESP_Signer::getTokenError() +{ + return getTokenError(tokenInfo); +} + +unsigned long ESP_Signer::getExpiredTimestamp() +{ + return config->signer.tokens.expires; +} + +void ESP_Signer::refreshToken() +{ + config->signer.tokens.expires = 0; + checkToken(); +} + +void ESP_Signer::errorToString(int httpCode, MB_String &buff) +{ + buff.clear(); + + if (config) + { + if (config->signer.tokens.status == esp_signer_token_status_error || config->signer.tokens.error.code != 0) + { + buff = config->signer.tokens.error.message; + return; + } + } + + switch (httpCode) + { + case ESP_SIGNER_ERROR_TCP_ERROR_CONNECTION_REFUSED: + buff += esp_signer_pgm_str_73; + return; + case ESP_SIGNER_ERROR_TCP_ERROR_SEND_HEADER_FAILED: + buff += esp_signer_pgm_str_74; + return; + case ESP_SIGNER_ERROR_TCP_ERROR_SEND_PAYLOAD_FAILED: + buff += esp_signer_pgm_str_75; + return; + case ESP_SIGNER_ERROR_TCP_ERROR_NOT_CONNECTED: + buff += esp_signer_pgm_str_28; + return; + case ESP_SIGNER_ERROR_TCP_ERROR_CONNECTION_LOST: + buff += esp_signer_pgm_str_29; + return; + case ESP_SIGNER_ERROR_TCP_ERROR_NO_HTTP_SERVER: + buff += esp_signer_pgm_str_76; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_BAD_REQUEST: + buff += esp_signer_pgm_str_77; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_NON_AUTHORITATIVE_INFORMATION: + buff += esp_signer_pgm_str_78; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_NO_CONTENT: + buff += esp_signer_pgm_str_79; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_MOVED_PERMANENTLY: + buff += esp_signer_pgm_str_80; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_USE_PROXY: + buff += esp_signer_pgm_str_81; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_TEMPORARY_REDIRECT: + buff += esp_signer_pgm_str_82; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_PERMANENT_REDIRECT: + buff += esp_signer_pgm_str_83; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_UNAUTHORIZED: + buff += esp_signer_pgm_str_84; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_FORBIDDEN: + buff += esp_signer_pgm_str_85; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_NOT_FOUND: + buff += esp_signer_pgm_str_86; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_METHOD_NOT_ALLOWED: + buff += esp_signer_pgm_str_87; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_NOT_ACCEPTABLE: + buff += esp_signer_pgm_str_88; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_PROXY_AUTHENTICATION_REQUIRED: + buff += esp_signer_pgm_str_89; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_REQUEST_TIMEOUT: + buff += esp_signer_pgm_str_30; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_LENGTH_REQUIRED: + buff += esp_signer_pgm_str_90; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_TOO_MANY_REQUESTS: + buff += esp_signer_pgm_str_91; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_REQUEST_HEADER_FIELDS_TOO_LARGE: + buff += esp_signer_pgm_str_92; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_INTERNAL_SERVER_ERROR: + buff += esp_signer_pgm_str_93; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_BAD_GATEWAY: + buff += esp_signer_pgm_str_94; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_SERVICE_UNAVAILABLE: + buff += esp_signer_pgm_str_95; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_GATEWAY_TIMEOUT: + buff += esp_signer_pgm_str_96; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_HTTP_VERSION_NOT_SUPPORTED: + buff += esp_signer_pgm_str_97; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_NETWORK_AUTHENTICATION_REQUIRED: + buff += esp_signer_pgm_str_98; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_PRECONDITION_FAILED: + buff += esp_signer_pgm_str_99; + return; + case ESP_SIGNER_ERROR_TCP_RESPONSE_PAYLOAD_READ_TIMED_OUT: + buff += esp_signer_pgm_str_100; + return; + case ESP_SIGNER_ERROR_TCP_ERROR_CONNECTION_INUSED: + buff += esp_signer_pgm_str_101; + return; + case ESP_SIGNER_ERROR_BUFFER_OVERFLOW: + buff += esp_signer_pgm_str_102; + return; + case ESP_SIGNER_ERROR_HTTP_CODE_PAYLOAD_TOO_LARGE: + buff += esp_signer_pgm_str_103; + return; + case ESP_SIGNER_ERROR_FILE_IO_ERROR: + buff += esp_signer_pgm_str_104; + return; + case ESP_SIGNER_ERROR_FILE_NOT_FOUND: + buff += esp_signer_pgm_str_105; + return; + case ESP_SIGNER_ERROR_TOKEN_NOT_READY: + buff += esp_signer_pgm_str_26; + return; + case ESP_SIGNER_ERROR_UNINITIALIZED: + buff += esp_signer_pgm_str_106; + return; + default: + return; + } +} + +bool ESP_Signer::setSystemTime(time_t ts) +{ + return ut->setTimestamp(ts) == 0; +} + +ESP_Signer Signer = ESP_Signer(); + +#endif \ No newline at end of file diff --git a/src/lib/ESPSigner/SignerConst.h b/src/lib/ESPSigner/SignerConst.h new file mode 100644 index 0000000..da9748d --- /dev/null +++ b/src/lib/ESPSigner/SignerConst.h @@ -0,0 +1,426 @@ + +/** + * Created April 23, 2022 + * + * + * The MIT License (MIT) + * Copyright (c) 2022 K. Suwatchai (Mobizt) + * + * + * Permission is hereby granted, free of charge, to any person returning a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef FB_COMMON_H_ +#define FB_COMMON_H_ + +#include +#include +#include +#include +#include + +#include +#include "./json/FirebaseJson.h" + +#if defined(ESP32) +#include +#include "./wcs/esp32/ESP_Signer_TCP_Client.h" +#elif defined(ESP8266) +#include +#include +#include +#include "./wcs/esp8266/ESP_Signer_TCP_Client.h" +#define FS_NO_GLOBALS +#endif + +#define SD_CS_PIN 15 +#define MAX_REDIRECT 5 +#define WIFI_RECONNECT_TIMEOUT 10000 +#define MAX_EXCHANGE_TOKEN_ATTEMPTS 5 +#define ESP_DEFAULT_TS 1618971013 +#define ESP_SIGNER_DEFAULT_RESPONSE_BUFFER_SIZE 2560 + +enum esp_signer_mem_storage_type +{ + esp_signer_mem_storage_type_undefined, + esp_signer_mem_storage_type_flash, + esp_signer_mem_storage_type_sd +}; + +enum esp_signer_auth_token_status +{ + esp_signer_token_status_uninitialized, + esp_signer_token_status_on_initialize, + esp_signer_token_status_on_signing, + esp_signer_token_status_on_request, + esp_signer_token_status_on_refresh, + esp_signer_token_status_ready, + esp_signer_token_status_error +}; + +enum esp_signer_auth_token_type +{ + esp_signer_token_type_undefined, + esp_signer_token_type_oauth2_access_token +}; + +enum esp_signer_jwt_generation_step +{ + esp_signer_jwt_generation_step_begin, + esp_signer_jwt_generation_step_encode_header_payload, + esp_signer_jwt_generation_step_sign, + esp_signer_jwt_generation_step_exchange +}; + +typedef struct esp_signer_spi_ethernet_module_t +{ +#if defined(ESP8266) && defined(ESP8266_CORE_SDK_V3_X_X) +#ifdef INC_ENC28J60_LWIP + ENC28J60lwIP *enc28j60; +#endif +#ifdef INC_W5100_LWIP + Wiznet5100lwIP *w5100; +#endif +#ifdef INC_W5500_LWIP + Wiznet5500lwIP *w5500; +#endif +#endif +} SPI_ETH_Module; + +struct esp_signer_url_info_t +{ + MB_String host; + MB_String uri; +}; + +struct esp_signer_server_response_data_t +{ + int httpCode = -1; + int payloadLen = -1; + int contentLen = -1; + int chunkRange = 0; + int payloadOfs = 0; + bool noContent = false; + bool isChunkedEnc = false; + MB_String location = ""; + MB_String contentType = ""; + MB_String connection = ""; + MB_String transferEnc = ""; +}; + +struct esp_signer_auth_token_error_t +{ + MB_String message; + int code = 0; +}; + +struct esp_signer_auth_token_info_t +{ + MB_String access_token; + MB_String auth_type; + MB_String jwt; + MB_String scope; + unsigned long expires = 0; + unsigned long last_millis = 0; + esp_signer_auth_token_type token_type = esp_signer_token_type_undefined; + esp_signer_auth_token_status status = esp_signer_token_status_uninitialized; + struct esp_signer_auth_token_error_t error; +}; + +struct esp_signer_service_account_data_info_t +{ + MB_String client_email; + MB_String client_id; + MB_String project_id; + MB_String private_key_id; + const char *private_key = ""; +}; + +struct esp_signer_auth_signin_user_t +{ + MB_String email; + MB_String password; +}; + +struct esp_signer_auth_cert_t +{ + const char *data = ""; + MB_String file; + esp_signer_mem_storage_type file_storage = esp_signer_mem_storage_type_flash; +}; + +struct esp_signer_service_account_file_info_t +{ + MB_String path; + esp_signer_mem_storage_type storage_type = esp_signer_mem_storage_type_flash; +}; + +struct esp_signer_service_account_t +{ + struct esp_signer_service_account_data_info_t data; + struct esp_signer_service_account_file_info_t json; +}; + +struct esp_signer_token_signer_resources_t +{ + int step = 0; + bool tokenTaskRunning = false; + unsigned long lastReqMillis = 0; + unsigned long preRefreshSeconds = 60; + unsigned long expiredSeconds = 3600; + unsigned long reqTO = 2000; + MB_String pk; + size_t hashSize = 32; // SHA256 size (256 bits or 32 bytes) + size_t signatureSize = 256; +#if defined(ESP32) + uint8_t *hash = nullptr; +#elif defined(ESP8266) + char *hash = nullptr; +#endif + unsigned char *signature = nullptr; + MB_String encHeader; + MB_String encPayload; + MB_String encHeadPayload; + MB_String encSignature; +#if defined(ESP32) + mbedtls_pk_context *pk_ctx = nullptr; + mbedtls_entropy_context *entropy_ctx = nullptr; + mbedtls_ctr_drbg_context *ctr_drbg_ctx = nullptr; + ESP_SIGNER_TCP_Client *wcs = nullptr; +#elif defined(ESP8266) + ESP_SIGNER_TCP_Client *wcs = nullptr; +#endif + FirebaseJson *json = nullptr; + FirebaseJsonData *result = nullptr; + struct esp_signer_auth_token_info_t tokens; + bool reuseSession = false; +}; + +struct esp_signer_cfg_int_t +{ + struct esp_signer_sd_config_info_t sd_config; + fs::File esp_signer_file; + bool esp_signer_sd_rdy = false; + bool esp_signer_flash_rdy = false; + bool esp_signer_sd_used = false; + bool esp_signer_reconnect_wifi = false; + unsigned long esp_signer_last_reconnect_millis = 0; + unsigned long esp_signer_last_jwt_begin_step_millis = 0; + uint16_t esp_signer_reconnect_tmo = WIFI_RECONNECT_TIMEOUT; + bool esp_signer_clock_rdy = false; + bool esp_signer_clock_synched = false; + float esp_signer_gmt_offset = 0; + const char *esp_signer_caCert = nullptr; + bool esp_signer_processing = false; + unsigned long esp_signer_last_jwt_generation_error_cb_millis = 0; + unsigned long esp_signer_last_time_sync_millis = 0; + unsigned long esp_signer_last_ntp_sync_timeout_millis = 0; + +#if defined(ESP32) + TaskHandle_t token_processing_task_handle = NULL; +#endif +}; + +struct esp_signer_client_timeout_t +{ + // WiFi reconnect timeout (interval) in ms (10 sec - 5 min) when WiFi disconnected. + uint16_t wifiReconnect = 10 * 1000; + + // Socket connection and ssl handshake timeout in ms (1 sec - 1 min). + unsigned long socketConnection = 10 * 1000; + + // unused. + unsigned long sslHandshake = 0; + + // Server response read timeout in ms (1 sec - 1 min). + unsigned long serverResponse = 10 * 1000; + + uint16_t tokenGenerationBeginStep = 300; + + uint16_t tokenGenerationError = 5 * 1000; + + uint16_t ntpServerRequest = 15 * 1000; +}; + +typedef struct token_info_t +{ + esp_signer_auth_token_type type = esp_signer_token_type_undefined; + esp_signer_auth_token_status status = esp_signer_token_status_uninitialized; + struct esp_signer_auth_token_error_t error; +} TokenInfo; + +typedef void (*TokenStatusCallback)(TokenInfo); + +struct esp_signer_cfg_t +{ + struct esp_signer_service_account_t service_account; + float time_zone = 0; + struct esp_signer_auth_cert_t cert; + struct esp_signer_token_signer_resources_t signer; + struct esp_signer_cfg_int_t internal; + TokenStatusCallback token_status_callback = NULL; + int8_t max_token_generation_retry = MAX_EXCHANGE_TOKEN_ATTEMPTS; + SPI_ETH_Module spi_ethernet_module; + struct esp_signer_client_timeout_t timeout; +}; + +struct esp_signer_session_info_t +{ + bool buffer_ovf = false; + bool chunked_encoding = false; + bool connected = false; + MB_String host = ""; + unsigned long last_conn_ms = 0; + const uint32_t conn_timeout = 3 * 60 * 1000; + + uint16_t resp_size = 2048; + int http_code = -1000; + int content_length = 0; + MB_String error = ""; + +#if defined(ESP8266) + uint16_t bssl_rx_size = 512; + uint16_t bssl_tx_size = 512; +#endif +}; + +typedef struct esp_signer_cfg_t SignerConfig; + +typedef std::function esp_signer_callback_function_t; + +// static const char esp_signer_pgm_str_1[] PROGMEM = "true"; +// static const char esp_signer_pgm_str_2[] PROGMEM = "double"; +static const char esp_signer_pgm_str_3[] PROGMEM = "Connection: "; +static const char esp_signer_pgm_str_4[] PROGMEM = "\r\n"; +static const char esp_signer_pgm_str_5[] PROGMEM = "Content-Type: "; +static const char esp_signer_pgm_str_6[] PROGMEM = "Content-Length: "; +static const char esp_signer_pgm_str_7[] PROGMEM = "Transfer-Encoding: "; +static const char esp_signer_pgm_str_8[] PROGMEM = "chunked"; +static const char esp_signer_pgm_str_9[] PROGMEM = "Location: "; +static const char esp_signer_pgm_str_10[] PROGMEM = ";"; +static const char esp_signer_pgm_str_11[] PROGMEM = "0.0.0.0"; +static const char esp_signer_pgm_str_12[] PROGMEM = "error_description"; +static const char esp_signer_pgm_str_13[] PROGMEM = "type"; +static const char esp_signer_pgm_str_14[] PROGMEM = "service_account"; +static const char esp_signer_pgm_str_15[] PROGMEM = "project_id"; +static const char esp_signer_pgm_str_16[] PROGMEM = "private_key_id"; +static const char esp_signer_pgm_str_17[] PROGMEM = "private_key"; +static const char esp_signer_pgm_str_18[] PROGMEM = "client_email"; +static const char esp_signer_pgm_str_19[] PROGMEM = "client_id"; +static const char esp_signer_pgm_str_20[] PROGMEM = "tokenProcessingTask"; +static const char esp_signer_pgm_str_21[] PROGMEM = "system time was not set"; +static const char esp_signer_pgm_str_22[] PROGMEM = "RSA private key parsing failed"; +static const char esp_signer_pgm_str_23[] PROGMEM = "create message digest"; +static const char esp_signer_pgm_str_24[] PROGMEM = "JWT token signing failed"; +static const char esp_signer_pgm_str_25[] PROGMEM = "token exchange failed"; +static const char esp_signer_pgm_str_26[] PROGMEM = "token is not ready"; +static const char esp_signer_pgm_str_27[] PROGMEM = "max token generation retry reached"; +static const char esp_signer_pgm_str_28[] PROGMEM = "not connected"; +static const char esp_signer_pgm_str_29[] PROGMEM = "connection lost"; +static const char esp_signer_pgm_str_30[] PROGMEM = "request timed out"; +static const char esp_signer_pgm_str_31[] PROGMEM = "HTTP/1.1 "; +static const char esp_signer_pgm_str_32[] PROGMEM = " "; +static const char esp_signer_pgm_str_33[] PROGMEM = "alg"; +static const char esp_signer_pgm_str_34[] PROGMEM = "RS256"; +static const char esp_signer_pgm_str_35[] PROGMEM = "JWT"; +static const char esp_signer_pgm_str_36[] PROGMEM = "typ"; +static const char esp_signer_pgm_str_37[] PROGMEM = "iss"; +static const char esp_signer_pgm_str_38[] PROGMEM = "sub"; +static const char esp_signer_pgm_str_39[] PROGMEM = "aud"; +static const char esp_signer_pgm_str_40[] PROGMEM = "https://"; +static const char esp_signer_pgm_str_41[] PROGMEM = "oauth2"; +static const char esp_signer_pgm_str_42[] PROGMEM = "."; +static const char esp_signer_pgm_str_43[] PROGMEM = "googleapis.com"; +static const char esp_signer_pgm_str_44[] PROGMEM = "/"; +static const char esp_signer_pgm_str_45[] PROGMEM = "token"; +static const char esp_signer_pgm_str_46[] PROGMEM = "iat"; +static const char esp_signer_pgm_str_47[] PROGMEM = "exp"; +static const char esp_signer_pgm_str_48[] PROGMEM = "www"; +static const char esp_signer_pgm_str_49[] PROGMEM = "undefined"; +static const char esp_signer_pgm_str_50[] PROGMEM = "OAuth2.0 access token"; +static const char esp_signer_pgm_str_51[] PROGMEM = "uninitialized"; +static const char esp_signer_pgm_str_52[] PROGMEM = "on initializing"; +static const char esp_signer_pgm_str_53[] PROGMEM = "on signing"; +static const char esp_signer_pgm_str_54[] PROGMEM = "on exchange request"; +static const char esp_signer_pgm_str_55[] PROGMEM = "on refreshing"; +static const char esp_signer_pgm_str_56[] PROGMEM = "scope"; +static const char esp_signer_pgm_str_57[] PROGMEM = "POST"; +static const char esp_signer_pgm_str_58[] PROGMEM = "grant_type"; +static const char esp_signer_pgm_str_59[] PROGMEM = "urn:ietf:params:oauth:grant-type:jwt-bearer"; +static const char esp_signer_pgm_str_60[] PROGMEM = "assertion"; +static const char esp_signer_pgm_str_61[] PROGMEM = " HTTP/1.1\r\n"; +static const char esp_signer_pgm_str_62[] PROGMEM = "Host: "; +// static const char esp_signer_pgm_str_63[] PROGMEM = "\r\n"; +static const char esp_signer_pgm_str_64[] PROGMEM = "User-Agent: ESP\r\n"; +static const char esp_signer_pgm_str_65[] PROGMEM = "Content-Length: "; +static const char esp_signer_pgm_str_66[] PROGMEM = "Content-Type: "; +static const char esp_signer_pgm_str_67[] PROGMEM = "application/json"; +static const char esp_signer_pgm_str_68[] PROGMEM = "error/code"; +static const char esp_signer_pgm_str_69[] PROGMEM = "error/message"; +static const char esp_signer_pgm_str_70[] PROGMEM = "access_token"; +static const char esp_signer_pgm_str_71[] PROGMEM = "token_type"; +static const char esp_signer_pgm_str_72[] PROGMEM = "expires_in"; +static const char esp_signer_pgm_str_73[] PROGMEM = "connection refused"; +static const char esp_signer_pgm_str_74[] PROGMEM = "send header failed"; +static const char esp_signer_pgm_str_75[] PROGMEM = "send payload failed"; +static const char esp_signer_pgm_str_76[] PROGMEM = "no HTTP server"; +static const char esp_signer_pgm_str_77[] PROGMEM = "bad request"; +static const char esp_signer_pgm_str_78[] PROGMEM = "non-authoriative information"; +static const char esp_signer_pgm_str_79[] PROGMEM = "no content"; +static const char esp_signer_pgm_str_80[] PROGMEM = "moved permanently"; +static const char esp_signer_pgm_str_81[] PROGMEM = "use proxy"; +static const char esp_signer_pgm_str_82[] PROGMEM = "temporary redirect"; +static const char esp_signer_pgm_str_83[] PROGMEM = "permanent redirect"; +static const char esp_signer_pgm_str_84[] PROGMEM = "unauthorized"; +static const char esp_signer_pgm_str_85[] PROGMEM = "forbidden"; +static const char esp_signer_pgm_str_86[] PROGMEM = "not found"; +static const char esp_signer_pgm_str_87[] PROGMEM = "method not allow"; +static const char esp_signer_pgm_str_88[] PROGMEM = "not acceptable"; +static const char esp_signer_pgm_str_89[] PROGMEM = "proxy authentication required"; +static const char esp_signer_pgm_str_90[] PROGMEM = "length required"; +static const char esp_signer_pgm_str_91[] PROGMEM = "too many requests"; +static const char esp_signer_pgm_str_92[] PROGMEM = "request header fields too larg"; +static const char esp_signer_pgm_str_93[] PROGMEM = "internal server error"; +static const char esp_signer_pgm_str_94[] PROGMEM = "bad gateway"; +static const char esp_signer_pgm_str_95[] PROGMEM = "service unavailable"; +static const char esp_signer_pgm_str_96[] PROGMEM = "gateway timeout"; +static const char esp_signer_pgm_str_97[] PROGMEM = "http version not support"; +static const char esp_signer_pgm_str_98[] PROGMEM = "network authentication required"; +static const char esp_signer_pgm_str_99[] PROGMEM = "precondition failed"; +static const char esp_signer_pgm_str_100[] PROGMEM = "read timed out"; +static const char esp_signer_pgm_str_101[] PROGMEM = "http connection was used by other processes"; +static const char esp_signer_pgm_str_102[] PROGMEM = "data buffer overflow"; +static const char esp_signer_pgm_str_103[] PROGMEM = "payload too large"; +static const char esp_signer_pgm_str_104[] PROGMEM = "File I/O error"; +static const char esp_signer_pgm_str_105[] PROGMEM = "File not found"; +static const char esp_signer_pgm_str_106[] PROGMEM = "Token generation was not initialized"; +static const char esp_signer_pgm_str_107[] PROGMEM = "https://%[^/]/%s"; +static const char esp_signer_pgm_str_108[] PROGMEM = "http://%[^/]/%s"; +static const char esp_signer_pgm_str_109[] PROGMEM = "%[^/]/%s"; +static const char esp_signer_pgm_str_110[] PROGMEM = "%[^?]?%s"; +static const char esp_signer_pgm_str_111[] PROGMEM = "?"; +static const char esp_signer_pgm_str_112[] PROGMEM = "ready"; +static const char esp_signer_pgm_str_113[] PROGMEM = "error"; +static const char esp_signer_pgm_str_114[] PROGMEM = "code: "; +static const char esp_signer_pgm_str_115[] PROGMEM = ", message: "; +static const char esp_signer_pgm_str_116[] PROGMEM = "NTP server time synching failed"; + +static const unsigned char esp_signer_base64_table[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char esp_signer_boundary_table[] PROGMEM = "=_abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +#endif \ No newline at end of file diff --git a/src/lib/ESPSigner/SignerUtils.h b/src/lib/ESPSigner/SignerUtils.h new file mode 100644 index 0000000..efa1fe9 --- /dev/null +++ b/src/lib/ESPSigner/SignerUtils.h @@ -0,0 +1,1712 @@ +/** + * Util class, SignerUtils.h version 1.0.5 + * + * + * Created April 23, 2022 + * + * This work is a part of ESP Signer library + * Copyright (c) 2022 K. Suwatchai (Mobizt) + * + * The MIT License (MIT) + * Copyright (c)2022 K. Suwatchai (Mobizt) + * + * + * Permission is hereby granted, free of charge, to any person returning a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef SIGNER_UTILS_H +#define SIGNER_UTILS_H + +#include +#include "SignerConst.h" + +class SignerUtils +{ + +public: + uint16_t ntpTimeout = 20; + esp_signer_callback_function_t _callback_function = nullptr; + SignerConfig *config = nullptr; + + SignerUtils(SignerConfig *cfg) + { + config = cfg; + }; + + ~SignerUtils(){}; + + bool ethLinkUp(SPI_ETH_Module *spi_ethernet_module = NULL) + { + bool ret = false; +#if defined(ESP32) + char *ip = strP(esp_signer_pgm_str_11); + if (strcmp(ETH.localIP().toString().c_str(), ip) != 0) + { + ret = true; + ETH.linkUp(); + } + delP(&ip); +#elif defined(ESP8266) && defined(ESP8266_CORE_SDK_V3_X_X) + + if (!spi_ethernet_module) + return ret; + +#if defined(INC_ENC28J60_LWIP) + if (spi_ethernet_module->enc28j60) + return spi_ethernet_module->enc28j60->status() == WL_CONNECTED; +#endif +#if defined(INC_W5100_LWIP) + if (spi_ethernet_module->w5100) + return spi_ethernet_module->w5100->status() == WL_CONNECTED; +#endif +#if defined(INC_W5100_LWIP) + if (spi_ethernet_module->w5500) + return spi_ethernet_module->w5500->status() == WL_CONNECTED; +#endif + +#endif + return ret; + } + + void ethDNSWorkAround(SPI_ETH_Module *spi_ethernet_module, const char *host, int port) + { +#if defined(ESP8266) && defined(ESP8266_CORE_SDK_V3_X_X) + + if (!spi_ethernet_module) + goto ex; + +#if defined(INC_ENC28J60_LWIP) + if (spi_ethernet_module->enc28j60) + goto ex; +#endif +#if defined(INC_W5100_LWIP) + if (spi_ethernet_module->w5100) + goto ex; +#endif +#if defined(INC_W5100_LWIP) + if (spi_ethernet_module->w5500) + goto ex; +#endif + return; + ex: + WiFiClient client; + client.connect(host, port); + client.stop(); + +#endif + } + + void idle() + { + delay(0); + } + char *strP(PGM_P pgm) + { + size_t len = strlen_P(pgm) + 5; + char *buf = (char *)newP(len); + strcpy_P(buf, pgm); + buf[strlen_P(pgm)] = 0; + return buf; + } + + int strposP(const char *buf, PGM_P beginH, int ofs) + { + char *tmp = strP(beginH); + int p = strpos(buf, tmp, ofs); + delP(&tmp); + return p; + } + + bool strcmpP(const char *buf, int ofs, PGM_P beginH) + { + char *tmp = nullptr; + if (ofs < 0) + { + int p = strposP(buf, beginH, 0); + if (p == -1) + return false; + ofs = p; + } + tmp = strP(beginH); + char *tmp2 = (char *)newP(strlen_P(beginH) + 1); + memcpy(tmp2, &buf[ofs], strlen_P(beginH)); + tmp2[strlen_P(beginH)] = 0; + bool ret = (strcasecmp(tmp, tmp2) == 0); + delP(&tmp); + delP(&tmp2); + return ret; + } + + char *subStr(const char *buf, PGM_P beginH, PGM_P endH, int beginPos, int endPos) + { + + char *tmp = nullptr; + int p1 = strposP(buf, beginH, beginPos); + if (p1 != -1) + { + int p2 = -1; + if (endPos == 0) + p2 = strposP(buf, endH, p1 + strlen_P(beginH)); + + if (p2 == -1) + p2 = strlen(buf); + + int len = p2 - p1 - strlen_P(beginH); + tmp = (char *)newP(len + 1); + memcpy(tmp, &buf[p1 + strlen_P(beginH)], len); + return tmp; + } + + return nullptr; + } + + void strcat_c(char *str, char c) + { + for (; *str; str++) + ; + *str++ = c; + *str++ = 0; + } + + int strpos(const char *haystack, const char *needle, int offset) + { + if (!haystack || !needle) + return -1; + + int hlen = strlen(haystack); + int nlen = strlen(needle); + + if (hlen == 0 || nlen == 0) + return -1; + + int hidx = offset, nidx = 0; + while ((*(haystack + hidx) != '\0') && (*(needle + nidx) != '\0') && hidx < hlen) + { + if (*(needle + nidx) != *(haystack + hidx)) + { + hidx++; + nidx = 0; + } + else + { + nidx++; + hidx++; + if (nidx == nlen) + return hidx - nidx; + } + } + + return -1; + } + + int strpos(const char *haystack, char needle, int offset) + { + if (!haystack || needle == 0) + return -1; + + int hlen = strlen(haystack); + + if (hlen == 0) + return -1; + + int hidx = offset; + while ((*(haystack + hidx) != '\0') && hidx < hlen) + { + if (needle == *(haystack + hidx)) + return hidx; + hidx++; + } + + return -1; + } + + int rstrpos(const char *haystack, const char *needle, int offset /* start search from this offset to the left string */) + { + if (!haystack || !needle) + return -1; + + int hlen = strlen(haystack); + int nlen = strlen(needle); + + if (hlen == 0 || nlen == 0) + return -1; + + int hidx = offset; + + if (hidx >= hlen || offset == -1) + hidx = hlen - 1; + + int nidx = nlen - 1; + + while (hidx >= 0) + { + if (*(needle + nidx) != *(haystack + hidx)) + { + hidx--; + nidx = nlen - 1; + } + else + { + if (nidx == 0) + return hidx + nidx; + nidx--; + hidx--; + } + } + + return -1; + } + + int rstrpos(const char *haystack, char needle, int offset /* start search from this offset to the left char */) + { + if (!haystack || needle == 0) + return -1; + + int hlen = strlen(haystack); + + if (hlen == 0) + return -1; + + int hidx = offset; + + if (hidx >= hlen || offset == -1) + hidx = hlen - 1; + + while (hidx >= 0) + { + if (needle == *(haystack + hidx)) + return hidx; + hidx--; + } + + return -1; + } + + void ltrim(MB_String &str, const MB_String &chars = " ") + { + size_t pos = str.find_first_not_of(chars); + if (pos != MB_String::npos) + str.erase(0, pos); + } + + void rtrim(MB_String &str, const MB_String &chars = " ") + { + size_t pos = str.find_last_not_of(chars); + if (pos != MB_String::npos) + str.erase(pos + 1); + } + + inline MB_String trim(const MB_String &s) + { + MB_String chars = " "; + MB_String str = s; + ltrim(str, chars); + rtrim(str, chars); + return str; + } + + void delP(void *ptr) + { + void **p = (void **)ptr; + if (*p) + { + free(*p); + *p = 0; + } + } + + size_t getReservedLen(size_t len) + { + int blen = len + 1; + + int newlen = (blen / 4) * 4; + + if (newlen < blen) + newlen += 4; + + return (size_t)newlen; + } + + void *newP(size_t len) + { + void *p; + size_t newLen = getReservedLen(len); +#if defined(BOARD_HAS_PSRAM) && defined(ESP_SIGNER_USE_PSRAM) + + if ((p = (void *)ps_malloc(newLen)) == 0) + return NULL; + +#else + +#if defined(ESP8266_USE_EXTERNAL_HEAP) + ESP.setExternalHeap(); +#endif + + bool nn = ((p = (void *)malloc(newLen)) > 0); + +#if defined(ESP8266_USE_EXTERNAL_HEAP) + ESP.resetHeap(); +#endif + + if (!nn) + return NULL; + +#endif + memset(p, 0, newLen); + return p; + } + + void substr(MB_String &str, const char *s, int offset, size_t len) + { + if (!s) + return; + + int slen = strlen(s); + + if (slen == 0) + return; + + int last = offset + len; + + if (offset >= slen || len == 0 || last > slen) + return; + + for (int i = offset; i < last; i++) + str += s[i]; + } + void splitString(const char *str, std::vector out, const char delim) + { + int current = 0, previous = 0; + current = strpos(str, delim, 0); + MB_String s; + while (current != -1) + { + s.clear(); + substr(s, str, previous, current - previous); + trim(s); + if (s.length() > 0) + out.push_back(s); + + previous = current + 1; + current = strpos(str, delim, previous); + delay(0); + } + + s.clear(); + + if (previous > 0 && current == -1) + substr(s, str, previous, strlen(str) - previous); + else + s = str; + + trim(s); + if (s.length() > 0) + out.push_back(s); + s.clear(); + } + + int url_decode(const char *s, char *dec) + { + char *o; + const char *end = s + strlen(s); + int c; + + for (o = dec; s <= end; o++) + { + c = *s++; + if (c == '+') + c = ' '; + else if (c == '%' && (!ishex(*s++) || + !ishex(*s++) || + !sscanf(s - 2, "%2x", &c))) + return -1; + + if (dec) + *o = c; + } + + return o - dec; + } + + MB_String url_encode(const MB_String &s) + { + MB_String ret; + ret.reserve(s.length() * 3 + 1); + for (size_t i = 0, l = s.size(); i < l; i++) + { + char c = s[i]; + if ((c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + c == '-' || c == '_' || c == '.' || c == '!' || c == '~' || + c == '*' || c == '\'' || c == '(' || c == ')') + { + ret += c; + } + else + { + ret += '%'; + unsigned char d1, d2; + hexchar(c, d1, d2); + ret += d1; + ret += d2; + } + } + ret.shrink_to_fit(); + return ret; + } + + inline int ishex(int x) + { + return (x >= '0' && x <= '9') || + (x >= 'a' && x <= 'f') || + (x >= 'A' && x <= 'F'); + } + + void hexchar(unsigned char c, unsigned char &hex1, unsigned char &hex2) + { + hex1 = c / 16; + hex2 = c % 16; + hex1 += hex1 <= 9 ? '0' : 'a' - 10; + hex2 += hex2 <= 9 ? '0' : 'a' - 10; + } + + char from_hex(char ch) + { + return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10; + } + + uint32_t hex2int(const char *hex) + { + uint32_t val = 0; + while (*hex) + { + // get current character then increment + uint8_t byte = *hex++; + // transform hex character to the 4bit equivalent number, using the ascii table indexes + if (byte >= '0' && byte <= '9') + byte = byte - '0'; + else if (byte >= 'a' && byte <= 'f') + byte = byte - 'a' + 10; + else if (byte >= 'A' && byte <= 'F') + byte = byte - 'A' + 10; + // shift 4 to make space for new digit, and add the 4 bits of the new digit + val = (val << 4) | (byte & 0xF); + } + return val; + } + + void parseRespHeader(const char *buf, struct esp_signer_server_response_data_t &response) + { + int beginPos = 0, pmax = 0, payloadPos = 0; + + char *tmp = nullptr; + + if (response.httpCode != -1) + { + payloadPos = beginPos; + pmax = beginPos; + tmp = getHeader(buf, esp_signer_pgm_str_3, esp_signer_pgm_str_4, beginPos, 0); + if (tmp) + { + response.connection = tmp; + delP(&tmp); + } + if (pmax < beginPos) + pmax = beginPos; + beginPos = payloadPos; + tmp = getHeader(buf, esp_signer_pgm_str_5, esp_signer_pgm_str_4, beginPos, 0); + if (tmp) + { + response.contentType = tmp; + delP(&tmp); + } + + if (pmax < beginPos) + pmax = beginPos; + beginPos = payloadPos; + tmp = getHeader(buf, esp_signer_pgm_str_6, esp_signer_pgm_str_4, beginPos, 0); + if (tmp) + { + response.contentLen = atoi(tmp); + delP(&tmp); + } + + if (pmax < beginPos) + pmax = beginPos; + beginPos = payloadPos; + tmp = getHeader(buf, esp_signer_pgm_str_7, esp_signer_pgm_str_4, beginPos, 0); + if (tmp) + { + response.transferEnc = tmp; + if (stringCompare(tmp, 0, esp_signer_pgm_str_8)) + response.isChunkedEnc = true; + delP(&tmp); + } + + if (pmax < beginPos) + pmax = beginPos; + beginPos = payloadPos; + tmp = getHeader(buf, esp_signer_pgm_str_3, esp_signer_pgm_str_4, beginPos, 0); + if (tmp) + { + response.connection = tmp; + delP(&tmp); + } + + if (pmax < beginPos) + pmax = beginPos; + beginPos = payloadPos; + tmp = getHeader(buf, esp_signer_pgm_str_6, esp_signer_pgm_str_4, beginPos, 0); + if (tmp) + { + + response.payloadLen = atoi(tmp); + delP(&tmp); + } + + if (response.httpCode == ESP_SIGNER_ERROR_HTTP_CODE_OK || response.httpCode == ESP_SIGNER_ERROR_HTTP_CODE_TEMPORARY_REDIRECT || response.httpCode == ESP_SIGNER_ERROR_HTTP_CODE_PERMANENT_REDIRECT || response.httpCode == ESP_SIGNER_ERROR_HTTP_CODE_MOVED_PERMANENTLY || response.httpCode == ESP_SIGNER_ERROR_HTTP_CODE_FOUND) + { + if (pmax < beginPos) + pmax = beginPos; + beginPos = payloadPos; + tmp = getHeader(buf, esp_signer_pgm_str_9, esp_signer_pgm_str_4, beginPos, 0); + if (tmp) + { + response.location = tmp; + delP(&tmp); + } + } + + if (response.httpCode == ESP_SIGNER_ERROR_HTTP_CODE_NO_CONTENT) + response.noContent = true; + } + } + + int readLine(WiFiClient *stream, char *buf, int bufLen) + { + int res = -1; + char c = 0; + int idx = 0; + if (!stream) + return idx; + while (stream->available() && idx <= bufLen) + { + if (!stream) + break; + res = stream->read(); + if (res > -1) + { + c = (char)res; + strcat_c(buf, c); + idx++; + if (c == '\n') + return idx; + } + } + return idx; + } + + int readLine(WiFiClient *stream, MB_String &buf) + { + int res = -1; + char c = 0; + int idx = 0; + if (!stream) + return idx; + while (stream->available()) + { + if (!stream) + break; + res = stream->read(); + if (res > -1) + { + c = (char)res; + buf += c; + idx++; + if (c == '\n') + return idx; + } + } + return idx; + } + + int readChunkedData(WiFiClient *stream, char *out, int &chunkState, int &chunkedSize, int &dataLen, int bufLen) + { + + char *tmp = nullptr; + char *buf = nullptr; + int p1 = 0; + int olen = 0; + + if (chunkState == 0) + { + chunkState = 1; + chunkedSize = -1; + dataLen = 0; + buf = (char *)newP(bufLen); + int readLen = readLine(stream, buf, bufLen); + if (readLen) + { + tmp = strP(esp_signer_pgm_str_10); + p1 = strpos(buf, tmp, 0); + delP(&tmp); + if (p1 == -1) + { + tmp = strP(esp_signer_pgm_str_4); + p1 = strpos(buf, tmp, 0); + delP(&tmp); + } + + if (p1 != -1) + { + tmp = (char *)newP(p1 + 1); + memcpy(tmp, buf, p1); + chunkedSize = hex2int(tmp); + delP(&tmp); + } + + // last chunk + if (chunkedSize < 1) + olen = -1; + } + else + chunkState = 0; + + delP(&buf); + } + else + { + + if (chunkedSize > -1) + { + buf = (char *)newP(bufLen); + int readLen = readLine(stream, buf, bufLen); + + if (readLen > 0) + { + // chunk may contain trailing + if (dataLen + readLen - 2 < chunkedSize) + { + dataLen += readLen; + memcpy(out, buf, readLen); + olen = readLen; + } + else + { + if (chunkedSize - dataLen > 0) + memcpy(out, buf, chunkedSize - dataLen); + dataLen = chunkedSize; + chunkState = 0; + olen = readLen; + } + } + else + { + olen = -1; + } + + delP(&buf); + } + } + + return olen; + } + + int readChunkedData(WiFiClient *stream, MB_String &out, int &chunkState, int &chunkedSize, int &dataLen) + { + + char *tmp = nullptr; + int p1 = 0; + int olen = 0; + + if (chunkState == 0) + { + chunkState = 1; + chunkedSize = -1; + dataLen = 0; + MB_String s; + int readLen = readLine(stream, s); + if (readLen) + { + tmp = strP(esp_signer_pgm_str_10); + p1 = strpos(s.c_str(), tmp, 0); + delP(&tmp); + if (p1 == -1) + { + tmp = strP(esp_signer_pgm_str_4); + p1 = strpos(s.c_str(), tmp, 0); + delP(&tmp); + } + + if (p1 != -1) + { + tmp = (char *)newP(p1 + 1); + memcpy(tmp, s.c_str(), p1); + chunkedSize = hex2int(tmp); + delP(&tmp); + } + + // last chunk + if (chunkedSize < 1) + olen = -1; + } + else + chunkState = 0; + } + else + { + + if (chunkedSize > -1) + { + MB_String s; + int readLen = readLine(stream, s); + + if (readLen > 0) + { + // chunk may contain trailing + if (dataLen + readLen - 2 < chunkedSize) + { + dataLen += readLen; + out += s; + olen = readLen; + } + else + { + if (chunkedSize - dataLen > 0) + out += s; + dataLen = chunkedSize; + chunkState = 0; + olen = readLen; + } + } + else + { + olen = -1; + } + } + } + + return olen; + } + + char *getHeader(const char *buf, PGM_P beginH, PGM_P endH, int &beginPos, int endPos) + { + + char *tmp = strP(beginH); + int p1 = strpos(buf, tmp, beginPos); + int ofs = 0; + delP(&tmp); + if (p1 != -1) + { + tmp = strP(endH); + int p2 = -1; + if (endPos > 0) + p2 = endPos; + else if (endPos == 0) + { + ofs = strlen_P(endH); + p2 = strpos(buf, tmp, p1 + strlen_P(beginH) + 1); + } + else if (endPos == -1) + { + beginPos = p1 + strlen_P(beginH); + } + + if (p2 == -1) + p2 = strlen(buf); + + delP(&tmp); + + if (p2 != -1) + { + beginPos = p2 + ofs; + int len = p2 - p1 - strlen_P(beginH); + tmp = (char *)newP(len + 1); + memcpy(tmp, &buf[p1 + strlen_P(beginH)], len); + return tmp; + } + } + + return nullptr; + } + + void getHeaderStr(const MB_String &in, MB_String &out, PGM_P beginH, PGM_P endH, int &beginPos, int endPos) + { + MB_String _in = in; + + char *tmp = strP(beginH); + int p1 = strpos(in.c_str(), tmp, beginPos); + int ofs = 0; + delP(&tmp); + if (p1 != -1) + { + tmp = strP(endH); + int p2 = -1; + if (endPos > 0) + p2 = endPos; + else if (endPos == 0) + { + ofs = strlen_P(endH); + p2 = strpos(in.c_str(), tmp, p1 + strlen_P(beginH) + 1); + } + else if (endPos == -1) + { + beginPos = p1 + strlen_P(beginH); + } + + if (p2 == -1) + p2 = in.length(); + + delP(&tmp); + + if (p2 != -1) + { + beginPos = p2 + ofs; + int len = p2 - p1 - strlen_P(beginH); + out = _in.substr(p1 + strlen_P(beginH), len); + } + } + } + + void closeFileHandle(bool sd) + { + if (!config) + return; + + if (config->internal.esp_signer_file) + config->internal.esp_signer_file.close(); + if (sd) + { + config->internal.esp_signer_sd_used = false; + config->internal.esp_signer_sd_rdy = false; +#if defined SD_FS + SD_FS.end(); +#endif + } + } + + bool decodeBase64Str(const MB_String &src, std::vector &out) + { + unsigned char *dtable = (unsigned char *)newP(256); + memset(dtable, 0x80, 256); + for (size_t i = 0; i < sizeof(esp_signer_base64_table) - 1; i++) + dtable[esp_signer_base64_table[i]] = (unsigned char)i; + dtable['='] = 0; + + unsigned char *block = (unsigned char *)newP(4); + unsigned char tmp; + size_t i, count; + int pad = 0; + size_t extra_pad; + size_t len = src.length(); + + count = 0; + + for (i = 0; i < len; i++) + { + if ((uint8_t)dtable[(uint8_t)src[i]] != 0x80) + count++; + } + + if (count == 0) + goto exit; + + extra_pad = (4 - count % 4) % 4; + + count = 0; + for (i = 0; i < len + extra_pad; i++) + { + unsigned char val; + + if (i >= len) + val = '='; + else + val = src[i]; + + tmp = dtable[val]; + + if (tmp == 0x80) + continue; + + if (val == '=') + pad++; + + block[count] = tmp; + count++; + if (count == 4) + { + out.push_back((block[0] << 2) | (block[1] >> 4)); + count = 0; + if (pad) + { + if (pad == 1) + out.push_back((block[1] << 4) | (block[2] >> 2)); + else if (pad > 2) + goto exit; + + break; + } + else + { + out.push_back((block[1] << 4) | (block[2] >> 2)); + out.push_back((block[2] << 6) | block[3]); + } + } + } + + delP(&block); + delP(&dtable); + + return true; + + exit: + delP(&block); + delP(&dtable); + return false; + } + + bool decodeBase64Flash(const char *src, size_t len, fs::File &file) + { + unsigned char *dtable = (unsigned char *)newP(256); + memset(dtable, 0x80, 256); + for (size_t i = 0; i < sizeof(esp_signer_base64_table) - 1; i++) + dtable[esp_signer_base64_table[i]] = (unsigned char)i; + dtable['='] = 0; + + unsigned char *block = (unsigned char *)newP(4); + unsigned char tmp; + size_t i, count; + int pad = 0; + size_t extra_pad; + count = 0; + + for (i = 0; i < len; i++) + { + if (dtable[(uint8_t)src[i]] != 0x80) + count++; + } + + if (count == 0) + goto exit; + + extra_pad = (4 - count % 4) % 4; + + count = 0; + for (i = 0; i < len + extra_pad; i++) + { + unsigned char val; + + if (i >= len) + val = '='; + else + val = src[i]; + tmp = dtable[val]; + if (tmp == 0x80) + continue; + + if (val == '=') + pad++; + + block[count] = tmp; + count++; + if (count == 4) + { + file.write((block[0] << 2) | (block[1] >> 4)); + count = 0; + if (pad) + { + if (pad == 1) + file.write((block[1] << 4) | (block[2] >> 2)); + else if (pad > 2) + goto exit; + break; + } + else + { + file.write((block[1] << 4) | (block[2] >> 2)); + file.write((block[2] << 6) | block[3]); + } + } + } + + delP(&block); + delP(&dtable); + + return true; + + exit: + delP(&block); + delP(&dtable); + + return false; + } + + void sendBase64Stream(WiFiClient *client, const MB_String &filePath, uint8_t storageType, fs::File &file) + { + + if (storageType == esp_signer_mem_storage_type_flash) + { +#if defined FLASH_FS + file = FLASH_FS.open(filePath.c_str(), "r"); +#endif + } + else if (storageType == esp_signer_mem_storage_type_sd) + { +#if defined SD_FS + file = SD_FS.open(filePath.c_str(), FILE_READ); +#endif + } + + if (!file) + return; + + size_t chunkSize = 512; + size_t fbuffSize = 3; + size_t byteAdd = 0; + size_t byteSent = 0; + + unsigned char *buff = (unsigned char *)newP(chunkSize); + memset(buff, 0, chunkSize); + + size_t len = file.size(); + size_t fbuffIndex = 0; + unsigned char *fbuff = (unsigned char *)newP(3); + + while (file.available()) + { + memset(fbuff, 0, fbuffSize); + if (len - fbuffIndex >= 3) + { + file.read(fbuff, 3); + + buff[byteAdd++] = esp_signer_base64_table[fbuff[0] >> 2]; + buff[byteAdd++] = esp_signer_base64_table[((fbuff[0] & 0x03) << 4) | (fbuff[1] >> 4)]; + buff[byteAdd++] = esp_signer_base64_table[((fbuff[1] & 0x0f) << 2) | (fbuff[2] >> 6)]; + buff[byteAdd++] = esp_signer_base64_table[fbuff[2] & 0x3f]; + + if (byteAdd >= chunkSize - 4) + { + byteSent += byteAdd; + client->write(buff, byteAdd); + memset(buff, 0, chunkSize); + byteAdd = 0; + } + + fbuffIndex += 3; + } + else + { + + if (len - fbuffIndex == 1) + { + fbuff[0] = file.read(); + } + else if (len - fbuffIndex == 2) + { + fbuff[0] = file.read(); + fbuff[1] = file.read(); + } + + break; + } + } + + file.close(); + + if (byteAdd > 0) + client->write(buff, byteAdd); + + if (len - fbuffIndex > 0) + { + + memset(buff, 0, chunkSize); + byteAdd = 0; + + buff[byteAdd++] = esp_signer_base64_table[fbuff[0] >> 2]; + if (len - fbuffIndex == 1) + { + buff[byteAdd++] = esp_signer_base64_table[(fbuff[0] & 0x03) << 4]; + buff[byteAdd++] = '='; + } + else + { + buff[byteAdd++] = esp_signer_base64_table[((fbuff[0] & 0x03) << 4) | (fbuff[1] >> 4)]; + buff[byteAdd++] = esp_signer_base64_table[(fbuff[1] & 0x0f) << 2]; + } + buff[byteAdd++] = '='; + + client->write(buff, byteAdd); + } + + delP(&buff); + delP(&fbuff); + } + + bool decodeBase64Stream(const char *src, size_t len, fs::File &file) + { + unsigned char *dtable = (unsigned char *)newP(256); + memset(dtable, 0x80, 256); + for (size_t i = 0; i < sizeof(esp_signer_base64_table) - 1; i++) + dtable[esp_signer_base64_table[i]] = (unsigned char)i; + dtable['='] = 0; + + unsigned char *block = (unsigned char *)newP(4); + unsigned char tmp; + size_t i, count; + int pad = 0; + size_t extra_pad; + + count = 0; + + for (i = 0; i < len; i++) + { + if (dtable[(uint8_t)src[i]] != 0x80) + count++; + } + + if (count == 0) + goto exit; + + extra_pad = (4 - count % 4) % 4; + + count = 0; + for (i = 0; i < len + extra_pad; i++) + { + unsigned char val; + + if (i >= len) + val = '='; + else + val = src[i]; + tmp = dtable[val]; + if (tmp == 0x80) + continue; + + if (val == '=') + pad++; + + block[count] = tmp; + count++; + if (count == 4) + { + file.write((block[0] << 2) | (block[1] >> 4)); + count = 0; + if (pad) + { + if (pad == 1) + file.write((block[1] << 4) | (block[2] >> 2)); + else if (pad > 2) + goto exit; + + break; + } + else + { + file.write((block[1] << 4) | (block[2] >> 2)); + file.write((block[2] << 6) | block[3]); + } + } + } + + delP(&block); + delP(&dtable); + + return true; + + exit: + + delP(&block); + delP(&dtable); + + return false; + } + + bool stringCompare(const char *buf, int ofs, PGM_P beginH) + { + char *tmp = strP(beginH); + char *tmp2 = (char *)newP(strlen_P(beginH) + 1); + memcpy(tmp2, &buf[ofs], strlen_P(beginH)); + tmp2[strlen_P(beginH)] = 0; + bool ret = (strcmp(tmp, tmp2) == 0); + delP(&tmp); + delP(&tmp2); + return ret; + } + + bool syncClock(float gmtOffset) + { + if (!config) + return false; + + time_t now = time(nullptr); + + config->internal.esp_signer_clock_rdy = now > ESP_DEFAULT_TS; + + if (config->internal.esp_signer_clock_rdy && gmtOffset == config->internal.esp_signer_gmt_offset) + return true; + + if (config->internal.esp_signer_reconnect_wifi) + reconnect(0); + + if (!config->internal.esp_signer_clock_rdy || gmtOffset != config->internal.esp_signer_gmt_offset) + { + if (gmtOffset != config->internal.esp_signer_gmt_offset) + config->internal.esp_signer_clock_synched = false; + + if (!config->internal.esp_signer_clock_synched) + configTime(gmtOffset * 3600, 0, "pool.ntp.org", "time.nist.gov"); + + config->internal.esp_signer_clock_synched = true; + + now = time(nullptr); + } + + config->internal.esp_signer_clock_rdy = now > ESP_DEFAULT_TS; + if (config->internal.esp_signer_clock_rdy) + config->internal.esp_signer_gmt_offset = gmtOffset; + + return config->internal.esp_signer_clock_rdy; + } + + void encodeBase64Url(char *encoded, unsigned char *string, size_t len) + { + size_t i; + char *p = encoded; + + unsigned char *b64enc = (unsigned char *)newP(65); + strcpy_P((char *)b64enc, (char *)esp_signer_base64_table); + b64enc[62] = '-'; + b64enc[63] = '_'; + + for (i = 0; i < len - 2; i += 3) + { + *p++ = b64enc[(string[i] >> 2) & 0x3F]; + *p++ = b64enc[((string[i] & 0x3) << 4) | ((int)(string[i + 1] & 0xF0) >> 4)]; + *p++ = b64enc[((string[i + 1] & 0xF) << 2) | ((int)(string[i + 2] & 0xC0) >> 6)]; + *p++ = b64enc[string[i + 2] & 0x3F]; + } + + if (i < len) + { + *p++ = b64enc[(string[i] >> 2) & 0x3F]; + if (i == (len - 1)) + { + *p++ = b64enc[((string[i] & 0x3) << 4)]; + } + else + { + *p++ = b64enc[((string[i] & 0x3) << 4) | ((int)(string[i + 1] & 0xF0) >> 4)]; + *p++ = b64enc[((string[i + 1] & 0xF) << 2)]; + } + } + + *p++ = '\0'; + + delP(&b64enc); + } + + bool sendBase64(uint8_t *data, size_t len, bool flashMem, ESP_SIGNER_TCP_Client *client) + { + bool ret = false; + const unsigned char *end, *in; + + end = data + len; + in = data; + + size_t chunkSize = 256; + size_t byteAdded = 0; + size_t byteSent = 0; + + unsigned char *buf = (unsigned char *)newP(chunkSize); + memset(buf, 0, chunkSize); + + unsigned char *tmp = (unsigned char *)newP(3); + + while (end - in >= 3) + { + memset(tmp, 0, 3); + if (flashMem) + memcpy_P(tmp, in, 3); + else + memcpy(tmp, in, 3); + + buf[byteAdded++] = esp_signer_base64_table[tmp[0] >> 2]; + buf[byteAdded++] = esp_signer_base64_table[((tmp[0] & 0x03) << 4) | (tmp[1] >> 4)]; + buf[byteAdded++] = esp_signer_base64_table[((tmp[1] & 0x0f) << 2) | (tmp[2] >> 6)]; + buf[byteAdded++] = esp_signer_base64_table[tmp[2] & 0x3f]; + + if (byteAdded >= chunkSize - 4) + { + byteSent += byteAdded; + + if (client->send((const char *)buf) != 0) + goto ex; + memset(buf, 0, chunkSize); + byteAdded = 0; + } + + in += 3; + } + + if (byteAdded > 0) + { + if (client->send((const char *)buf) != 0) + goto ex; + } + + if (end - in) + { + memset(buf, 0, chunkSize); + byteAdded = 0; + memset(tmp, 0, 3); + if (flashMem) + { + if (end - in == 1) + memcpy_P(tmp, in, 1); + else + memcpy_P(tmp, in, 2); + } + else + { + if (end - in == 1) + memcpy(tmp, in, 1); + else + memcpy(tmp, in, 2); + } + + buf[byteAdded++] = esp_signer_base64_table[tmp[0] >> 2]; + if (end - in == 1) + { + buf[byteAdded++] = esp_signer_base64_table[(tmp[0] & 0x03) << 4]; + buf[byteAdded++] = '='; + } + else + { + buf[byteAdded++] = esp_signer_base64_table[((tmp[0] & 0x03) << 4) | (tmp[1] >> 4)]; + buf[byteAdded++] = esp_signer_base64_table[(tmp[1] & 0x0f) << 2]; + } + buf[byteAdded++] = '='; + + if (client->send((const char *)buf) != 0) + goto ex; + memset(buf, 0, chunkSize); + } + + ret = true; + ex: + + delP(&tmp); + delP(&buf); + return ret; + } + + MB_String encodeBase64Str(const unsigned char *src, size_t len) + { + return encodeBase64Str((uint8_t *)src, len); + } + + MB_String encodeBase64Str(uint8_t *src, size_t len) + { + unsigned char *out, *pos; + const unsigned char *end, *in; + + unsigned char *b64enc = (unsigned char *)newP(65); + strcpy_P((char *)b64enc, (char *)esp_signer_base64_table); + + size_t olen; + + olen = 4 * ((len + 2) / 3); /* 3-byte blocks to 4-byte */ + + MB_String outStr; + outStr.resize(olen); + out = (unsigned char *)&outStr[0]; + + end = src + len; + in = src; + pos = out; + + while (end - in >= 3) + { + *pos++ = b64enc[in[0] >> 2]; + *pos++ = b64enc[((in[0] & 0x03) << 4) | (in[1] >> 4)]; + *pos++ = b64enc[((in[1] & 0x0f) << 2) | (in[2] >> 6)]; + *pos++ = b64enc[in[2] & 0x3f]; + in += 3; + delay(0); + } + + if (end - in) + { + + *pos++ = b64enc[in[0] >> 2]; + + if (end - in == 1) + { + *pos++ = b64enc[(in[0] & 0x03) << 4]; + *pos++ = '='; + } + else + { + *pos++ = b64enc[((in[0] & 0x03) << 4) | (in[1] >> 4)]; + *pos++ = b64enc[(in[1] & 0x0f) << 2]; + } + + *pos++ = '='; + } + + delP(&b64enc); + return outStr; + } + + size_t base64EncLen(size_t len) + { + return ((len + 2) / 3 * 4) + 1; + } + +#if defined(CARD_TYPE_SD) + bool sdBegin(int8_t ss, int8_t sck, int8_t miso, int8_t mosi) + { +#if defined SD_FS + if (!config) + return false; + + if (config) + { + config->internal.sd_config.sck = sck; + config->internal.sd_config.miso = miso; + config->internal.sd_config.mosi = mosi; + config->internal.sd_config.ss = ss; + } +#if defined(ESP32) + if (ss > -1) + { + SPI.begin(sck, miso, mosi, ss); + return SD_FS.begin(ss, SPI); + } + else + return SD_FS.begin(); +#elif defined(ESP8266) + if (ss > -1) + return SD_FS.begin(ss); + else + return SD_FS.begin(SD_CS_PIN); +#endif +#else + return false; +#endif + } +#endif + +#if defined(ESP32) +#if defined(CARD_TYPE_SD_MMC) + bool sdBegin(const char *mountpoint, bool mode1bit, bool format_if_mount_failed) + { + if (!config) + return false; + + if (config) + { + config->internal.sd_config.sd_mmc_mountpoint = mountpoint; + config->internal.sd_config.sd_mmc_mode1bit = mode1bit; + config->internal.sd_config.sd_mmc_format_if_mount_failed = format_if_mount_failed; + } + return SD_FS.begin(mountpoint, mode1bit, format_if_mount_failed); + } +#endif +#endif + + bool flashTest() + { +#if defined FLASH_FS + if (!config) + return false; +#if defined(ESP32) + if (FORMAT_FLASH == 1) + config->internal.esp_signer_flash_rdy = FLASH_FS.begin(true); + else + config->internal.esp_signer_flash_rdy = FLASH_FS.begin(); +#elif defined(ESP8266) + config->internal.esp_signer_flash_rdy = FLASH_FS.begin(); +#endif + return config->internal.esp_signer_flash_rdy; +#else + return false; +#endif + } + + bool sdTest(fs::File file) + { +#if defined SD_FS + if (!config) + return false; + + MB_String filepath = "/sdtest01.txt"; +#if defined(CARD_TYPE_SD) + if (!sdBegin(config->internal.sd_config.ss, config->internal.sd_config.sck, config->internal.sd_config.miso, config->internal.sd_config.mosi)) + return false; +#endif +#if defined(ESP32) +#if defined(CARD_TYPE_SD_MMC) + if (!sdBegin(config->internal.sd_config.sd_mmc_mountpoint, config->internal.sd_config.sd_mmc_mode1bit, config->internal.sd_config.sd_mmc_format_if_mount_failed)) + return false; +#endif +#endif + file = SD_FS.open(filepath.c_str(), FILE_WRITE); + if (!file) + return false; + + if (!file.write(32)) + { + file.close(); + return false; + } + + file.close(); + + file = SD_FS.open(filepath.c_str()); + if (!file) + return false; + + while (file.available()) + { + if (file.read() != 32) + { + file.close(); + return false; + } + } + file.close(); + + SD_FS.remove(filepath.c_str()); + + MB_String().swap(filepath); + + config->internal.esp_signer_sd_rdy = true; + + return true; +#else + return false; +#endif + } + +#if defined(ESP8266) + void set_scheduled_callback(esp_signer_callback_function_t callback) + { + _callback_function = std::move([callback]() + { schedule_function(callback); }); + _callback_function(); + } +#endif + + bool waitIdle(int &httpCode) + { +#if defined(ESP32) + + unsigned long wTime = millis(); + while (config->internal.esp_signer_processing) + { + if (millis() - wTime > 3000) + { + httpCode = ESP_SIGNER_ERROR_TCP_ERROR_CONNECTION_INUSED; + return false; + } + delay(0); + } +#endif + return true; + } + + void splitTk(const MB_String &str, std::vector &tk, const char *delim) + { + std::size_t current, previous = 0; + current = str.find(delim, previous); + MB_String s; + while (current != MB_String::npos) + { + s = str.substr(previous, current - previous); + tk.push_back(s); + previous = current + strlen(delim); + current = str.find(delim, previous); + } + s = str.substr(previous, current - previous); + tk.push_back(s); + MB_String().swap(s); + } + + bool reconnect(unsigned long dataTime) + { + + bool status = WiFi.status() == WL_CONNECTED; + + if (config) + status |= ethLinkUp(&config->spi_ethernet_module); + + if (dataTime > 0) + { + if (config) + { + if (config->timeout.serverResponse < 1000 || config->timeout.serverResponse > 1000) + config->timeout.serverResponse = 10000; + + if (millis() - dataTime > config->timeout.serverResponse) + return false; + } + else + { + if (millis() - dataTime > 10 * 1000) + return false; + } + } + + if (!status) + { + + if (config) + { + if (config->internal.esp_signer_reconnect_wifi) + { + if (config->timeout.wifiReconnect < 10000 || config->timeout.wifiReconnect > 5 * 60 * 1000) + config->timeout.wifiReconnect = 10000; + if (millis() - config->internal.esp_signer_last_reconnect_millis > config->timeout.wifiReconnect) + { + WiFi.reconnect(); + config->internal.esp_signer_last_reconnect_millis = millis(); + } + } + } + + status = WiFi.status() == WL_CONNECTED; + + if (config) + status |= ethLinkUp(&config->spi_ethernet_module); + } + + return status; + } + + int setTimestamp(time_t ts) + { + struct timeval tm = {ts, 0}; // sec, us + return settimeofday((const timeval *)&tm, 0); + } + +private: +}; + +#endif \ No newline at end of file