Skip to content

Respect timeout with SSL #8899

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cores/esp8266/Stream.h
Original file line number Diff line number Diff line change
@@ -65,7 +65,7 @@ class Stream: public Print {

// parsing methods

void setTimeout(unsigned long timeout); // sets maximum milliseconds to wait for stream data, default is 1 second
virtual void setTimeout(unsigned long timeout); // sets maximum milliseconds to wait for stream data, default is 1 second
unsigned long getTimeout () const { return _timeout; }

bool find(const char *target); // reads data from the stream until the target string is found
Original file line number Diff line number Diff line change
@@ -41,11 +41,33 @@ void loop() {

std::unique_ptr<BearSSL::WiFiClientSecure> client(new BearSSL::WiFiClientSecure);

#if 1 // 1=secure, 0=insecure

client->setFingerprint(fingerprint_sni_cloudflaressl_com);

// date needs to be setup
configTime("UTC", "pool.ntp.org");
time_t now;
while (time(&now) < 24 * 3600) {
Serial.println("waiting for NTP time to be set...");
delay(1000);
}
Serial.printf("date: %s", ctime(&now));

#else
// Or, if you happy to ignore the SSL certificate, then use the following line instead:
// client->setInsecure();
client->setInsecure();
#endif

HTTPClient https;
https.setWallTime(10000); // do not exceed 10s while getting data
client->setWallTime(20000); // do not exceed 20s during handshake

// Try to reduce RAM footprint when SSL server allows it
constexpr int sslbufsize = 1024;
bool mfln = client->probeMaxFragmentLength(jigsaw_host, jigsaw_port, sslbufsize);
Serial.printf("Can reduce SSL footprint to %d bytes in RAM: %s\n", sslbufsize, mfln ? "yes" : "no");
if (mfln) { client->setBufferSizes(sslbufsize, sslbufsize); }

Serial.print("[HTTPS] begin...\n");
if (https.begin(*client, jigsaw_host, jigsaw_port)) { // HTTPS
20 changes: 6 additions & 14 deletions libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp
Original file line number Diff line number Diff line change
@@ -216,7 +216,7 @@ void HTTPClient::disconnect(bool preserveClient)
}

if(_reuse && _canReuse) {
DEBUG_HTTPCLIENT("[HTTP-Client][end] tcp keep open for reuse\n");
DEBUG_HTTPCLIENT("[HTTP-Client][end] tcp kept open for reuse\n");
} else {
DEBUG_HTTPCLIENT("[HTTP-Client][end] tcp stop\n");
if(_client) {
@@ -303,15 +303,12 @@ void HTTPClient::setAuthorization(String auth)
}

/**
* set the timeout for the TCP connection
* set the wall time for operations
* @param timeout unsigned int
*/
void HTTPClient::setTimeout(uint16_t timeout)
void HTTPClient::setWallTime(unsigned long wallTime)
{
_tcpTimeout = timeout;
if(connected()) {
_client->setTimeout(timeout);
}
_wallTime.reset(wallTime?: esp8266::polledTimeout::oneShotMs::neverExpires);
}

/**
@@ -794,8 +791,6 @@ bool HTTPClient::connect(void)
return false;
}

_client->setTimeout(_tcpTimeout);

if(!_client->connect(_host.c_str(), _port)) {
DEBUG_HTTPCLIENT("[HTTP-Client] failed connect to %s:%u\n", _host.c_str(), _port);
return false;
@@ -879,7 +874,6 @@ bool HTTPClient::sendHeader(const char * type)
*/
int HTTPClient::handleHeaderResponse()
{

if(!connected()) {
return HTTPC_ERROR_NOT_CONNECTED;
}
@@ -891,16 +885,14 @@ int HTTPClient::handleHeaderResponse()
String transferEncoding;

_transferEncoding = HTTPC_TE_IDENTITY;
unsigned long lastDataTime = millis();
_wallTime.reset();

while(connected()) {
size_t len = _client->available();
if(len > 0) {
int headerSeparator = -1;
String headerLine = _client->readStringUntil('\n');

lastDataTime = millis();

DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] RX: '%s'\n", headerLine.c_str());

if (headerLine.startsWith(F("HTTP/1."))) {
@@ -978,7 +970,7 @@ int HTTPClient::handleHeaderResponse()
}

} else {
if((millis() - lastDataTime) > _tcpTimeout) {
if(_wallTime) {
return HTTPC_ERROR_READ_TIMEOUT;
}
esp_yield();
13 changes: 10 additions & 3 deletions libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.h
Original file line number Diff line number Diff line change
@@ -29,6 +29,7 @@
#include <Arduino.h>
#include <StreamString.h>
#include <WiFiClient.h>
#include <PolledTimeout.h>

#include <memory>

@@ -152,7 +153,7 @@ typedef std::unique_ptr<TransportTraits> TransportTraitsPtr;
class HTTPClient
{
public:
HTTPClient() = default;
HTTPClient(): _wallTime(HTTPCLIENT_DEFAULT_TCP_TIMEOUT) {};
~HTTPClient() = default;
HTTPClient(HTTPClient&&) = default;
HTTPClient& operator=(HTTPClient&&) = default;
@@ -177,7 +178,8 @@ class HTTPClient
void setAuthorization(const char * user, const char * password);
void setAuthorization(const char * auth);
void setAuthorization(String auth);
void setTimeout(uint16_t timeout);
void setWallTime(unsigned long wallTime);
[[deprecated("use setWallTime() instead")]] void setTimeout(unsigned long wallTime) { setWallTime(wallTime); }

// Redirections
void setFollowRedirects(followRedirects_t follow);
@@ -256,7 +258,7 @@ class HTTPClient
String _host;
uint16_t _port = 0;
bool _reuse = true;
uint16_t _tcpTimeout = HTTPCLIENT_DEFAULT_TCP_TIMEOUT;
esp8266::polledTimeout::oneShotMs _wallTime;
bool _useHTTP10 = false;

String _uri;
@@ -314,6 +316,7 @@ int HTTPClient::writeToStream(S * output)
}
} else if(_transferEncoding == HTTPC_TE_CHUNKED) {
int size = 0;
_wallTime.reset();
while(1) {
if(!connected()) {
return returnError(HTTPC_ERROR_CONNECTION_LOST);
@@ -360,6 +363,10 @@ int HTTPClient::writeToStream(S * output)
return returnError(HTTPC_ERROR_READ_TIMEOUT);
}

if (_wallTime) {
return returnError(HTTPC_ERROR_READ_TIMEOUT);
}

esp_yield();
}
} else {
10 changes: 8 additions & 2 deletions libraries/ESP8266WiFi/src/WiFiClient.cpp
Original file line number Diff line number Diff line change
@@ -216,7 +216,7 @@ size_t WiFiClient::write(const uint8_t *buf, size_t size)
{
return 0;
}
_client->setTimeout(_timeout);
_client->setTimeout(_timeout); // context write uses timeout
return _client->write((const char*)buf, size);
}

@@ -238,7 +238,6 @@ size_t WiFiClient::write_P(PGM_P buf, size_t size)
{
return 0;
}
_client->setTimeout(_timeout);
StreamConstPtr nopeek(buf, size);
return nopeek.sendAll(this);
}
@@ -463,3 +462,10 @@ void WiFiClient::peekConsume (size_t consume)
if (_client)
_client->peekConsume(consume);
}

void WiFiClient::setTimeout (unsigned long timeout)
{
Client::setTimeout(timeout);
if (_client)
_client->setTimeout(timeout);
}
1 change: 1 addition & 0 deletions libraries/ESP8266WiFi/src/WiFiClient.h
Original file line number Diff line number Diff line change
@@ -64,6 +64,7 @@ class WiFiClient : public Client, public SList<WiFiClient> {
// - https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rh-copy
virtual std::unique_ptr<WiFiClient> clone() const;

virtual void setTimeout (unsigned long timeout) override;
virtual uint8_t status();
virtual int connect(IPAddress ip, uint16_t port) override;
virtual int connect(const char *host, uint16_t port) override;
44 changes: 31 additions & 13 deletions libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.cpp
Original file line number Diff line number Diff line change
@@ -31,7 +31,6 @@ extern "C" {
}
#include "debug.h"
#include "ESP8266WiFi.h"
#include "PolledTimeout.h"
#include "WiFiClient.h"
#include "WiFiClientSecureBearSSL.h"
#include "StackThunk.h"
@@ -68,10 +67,9 @@ extern "C" {

namespace BearSSL {

void WiFiClientSecureCtx::_clear() {
// TLS handshake may take more than the 5 second default timeout
_timeout = 15000;
constexpr auto defaultWallTime = 10000UL;

void WiFiClientSecureCtx::_clear() {
_sc = nullptr;
_sc_svr = nullptr;
_eng = nullptr;
@@ -103,7 +101,7 @@ void WiFiClientSecureCtx::_clearAuthenticationSettings() {
}


WiFiClientSecureCtx::WiFiClientSecureCtx() : WiFiClient() {
WiFiClientSecureCtx::WiFiClientSecureCtx() : WiFiClient(), _wallTime(defaultWallTime) {
_clear();
_clearAuthenticationSettings();
_certStore = nullptr; // Don't want to remove cert store on a clear, should be long lived
@@ -124,7 +122,7 @@ WiFiClientSecureCtx::~WiFiClientSecureCtx() {
WiFiClientSecureCtx::WiFiClientSecureCtx(ClientContext* client,
const X509List *chain, const PrivateKey *sk,
int iobuf_in_size, int iobuf_out_size, ServerSessions *cache,
const X509List *client_CA_ta, int tls_min, int tls_max) {
const X509List *client_CA_ta, int tls_min, int tls_max): _wallTime(defaultWallTime) {
_clear();
_clearAuthenticationSettings();
stack_thunk_add_ref();
@@ -145,7 +143,7 @@ WiFiClientSecureCtx::WiFiClientSecureCtx(ClientContext *client,
const X509List *chain,
unsigned cert_issuer_key_type, const PrivateKey *sk,
int iobuf_in_size, int iobuf_out_size, ServerSessions *cache,
const X509List *client_CA_ta, int tls_min, int tls_max) {
const X509List *client_CA_ta, int tls_min, int tls_max): _wallTime(defaultWallTime) {
_clear();
_clearAuthenticationSettings();
stack_thunk_add_ref();
@@ -204,7 +202,10 @@ bool WiFiClientSecureCtx::stop(unsigned int maxWaitMs) {
}

bool WiFiClientSecureCtx::flush(unsigned int maxWaitMs) {
auto savedTimeout = _timeout;
_timeout = std::max(1U, maxWaitMs);
(void) _run_until(BR_SSL_SENDAPP);
_timeout = savedTimeout;
return WiFiClient::flush(maxWaitMs);
}

@@ -246,7 +247,6 @@ void WiFiClientSecureCtx::_freeSSL() {
_recvapp_len = 0;
// This connection is toast
_handshake_done = false;
_timeout = 15000;
}

bool WiFiClientSecureCtx::_clientConnected() {
@@ -462,7 +462,7 @@ size_t WiFiClientSecureCtx::peekBytes(uint8_t *buffer, size_t length) {
}

_startMillis = millis();
while ((_pollRecvBuffer() < (int) length) && ((millis() - _startMillis) < 5000)) {
while ((_pollRecvBuffer() < (int)length) && ((millis() - _startMillis) < _timeout)) {
yield();
}

@@ -539,6 +539,7 @@ int WiFiClientSecureCtx::_run_until(unsigned target, bool blocking) {
br_ssl_engine_sendrec_ack(_eng, wlen);
}
no_work = 0;
loopTimeout.reset();
continue;
}

@@ -582,6 +583,7 @@ int WiFiClientSecureCtx::_run_until(unsigned target, bool blocking) {
br_ssl_engine_recvrec_ack(_eng, rlen);
}
no_work = 0;
loopTimeout.reset();
continue;
}
}
@@ -602,6 +604,14 @@ int WiFiClientSecureCtx::_run_until(unsigned target, bool blocking) {
}

bool WiFiClientSecureCtx::_wait_for_handshake() {

#if defined(DEBUG_ESP_PORT)
if constexpr (F_CPU != 160000000L) {
DEBUG_ESP_PORT.printf_P((PGM_P)PSTR("BSSL: Please enable 160MHz build\n"));
}
#endif

_wallTime.reset();
_handshake_done = false;
while (!_handshake_done && _clientConnected()) {
int ret = _run_until(BR_SSL_SENDAPP);
@@ -612,6 +622,10 @@ bool WiFiClientSecureCtx::_wait_for_handshake() {
if (br_ssl_engine_current_state(_eng) & BR_SSL_SENDAPP) {
_handshake_done = true;
}
if (_wallTime) {
DEBUG_BSSL("handshake too long\n");
break;
}
optimistic_yield(1000);
}
return _handshake_done;
@@ -1206,9 +1220,6 @@ bool WiFiClientSecureCtx::_connectSSL(const char* hostName) {
_x509_insecure = nullptr;
_x509_knownkey = nullptr;

// reduce timeout after successful handshake to fail fast if server stop accepting our data for whathever reason
if (ret) _timeout = 5000;

return ret;
}

@@ -1673,4 +1684,11 @@ bool WiFiClientSecure::probeMaxFragmentLength(IPAddress ip, uint16_t port, uint1
return _SendAbort(probe, supportsLen);
}

};
void WiFiClientSecure::setTimeout (unsigned long timeout)
{
WiFiClient::setTimeout(timeout);
if (_ctx)
_ctx->setTimeout(timeout);
}

}; // namespace BearSSL
12 changes: 12 additions & 0 deletions libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.h
Original file line number Diff line number Diff line change
@@ -28,6 +28,7 @@
#include <bearssl/bearssl.h>
#include "BearSSLHelpers.h"
#include "CertStoreBearSSL.h"
#include "PolledTimeout.h"

namespace BearSSL {

@@ -147,6 +148,9 @@ class WiFiClientSecureCtx : public WiFiClient {
// consume bytes after use (see peekBuffer)
virtual void peekConsume (size_t consume) override;

// install a wall-time used during handshake
void setWallTime (unsigned long wallTime) { _wallTime.reset(wallTime?: esp8266::polledTimeout::oneShotMs::neverExpires); }

protected:
bool _connectSSL(const char *hostName); // Do initial SSL handshake

@@ -235,6 +239,8 @@ class WiFiClientSecureCtx : public WiFiClient {
bool _installServerX509Validator(const X509List *client_CA_ta); // Setup X509 client cert validation, if supplied

uint8_t *_streamLoad(Stream& stream, size_t size);

esp8266::polledTimeout::oneShotMs _wallTime;
}; // class WiFiClientSecureCtx


@@ -373,6 +379,12 @@ class WiFiClientSecure : public WiFiClient {

void disableKeepAlive() override { _ctx->disableKeepAlive(); };

// override setTimeout and forward to context
virtual void setTimeout (unsigned long timeout) override;

// install a wall-time used during handshake
void setWallTime (unsigned long wallTime) { _ctx->setWallTime(wallTime); }

private:
std::shared_ptr<WiFiClientSecureCtx> _ctx;

Original file line number Diff line number Diff line change
@@ -27,7 +27,7 @@ void setup() {

Serial.println();

ESPhttpUpdate.setClientTimeout(2000); // default was 8000
ESPhttpUpdate.setWallTime(2000); // default was 8000

WiFi.mode(WIFI_STA);
WiFiMulti.addAP(APSSID, APPSK);
8 changes: 4 additions & 4 deletions libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp
Original file line number Diff line number Diff line change
@@ -28,12 +28,12 @@
#include <flash_hal.h>

ESP8266HTTPUpdate::ESP8266HTTPUpdate(void)
: _httpClientTimeout(8000)
: _httpWallTime(8000)
{
}

ESP8266HTTPUpdate::ESP8266HTTPUpdate(int httpClientTimeout)
: _httpClientTimeout(httpClientTimeout)
ESP8266HTTPUpdate::ESP8266HTTPUpdate(int httpWallTime)
: _httpWallTime(httpWallTime)
{
}

@@ -164,7 +164,7 @@ HTTPUpdateResult ESP8266HTTPUpdate::handleUpdate(HTTPClient& http, const String&

// use HTTP/1.0 for update since the update handler not support any transfer Encoding
http.useHTTP10(true);
http.setTimeout(_httpClientTimeout);
http.setWallTime(_httpWallTime);
http.setFollowRedirects(_followRedirects);
http.setUserAgent(F("ESP8266-http-Update"));
http.addHeader(F("x-ESP8266-Chip-ID"), String(ESP.getChipId()));
7 changes: 3 additions & 4 deletions libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.h
Original file line number Diff line number Diff line change
@@ -132,9 +132,8 @@ class ESP8266HTTPUpdate
int getLastError(void);
String getLastErrorString(void);

void setClientTimeout(int timeout) {
_httpClientTimeout = timeout;
}
[[deprecated("use setWallTime()")]] void setClientTimeout(int wallTime) { setWallTime(wallTime); }
void setWallTime(int wallTime) { _httpWallTime = wallTime; }
protected:
t_httpUpdate_return handleUpdate(HTTPClient& http, const String& currentVersion, bool spiffs = false);
bool runUpdate(Stream& in, uint32_t size, const String& md5, int command = U_FLASH);
@@ -154,7 +153,7 @@ class ESP8266HTTPUpdate
String _auth;
String _md5Sum;
private:
int _httpClientTimeout;
unsigned long _httpWallTime;
followRedirects_t _followRedirects = HTTPC_DISABLE_FOLLOW_REDIRECTS;

// Callbacks