debugged stack overflow error, fixed connection timeout and some error flow problems

This commit is contained in:
Noah Laptop 2019-03-12 16:59:45 -07:00
parent c212e355a4
commit 7f72073fa6
5 changed files with 129 additions and 51 deletions

View file

@ -67,6 +67,7 @@ class SSLClient : public SSLClientImpl {
*/ */
static_assert(std::is_base_of<Client, C>::value, "C must be a Client Class!"); static_assert(std::is_base_of<Client, C>::value, "C must be a Client Class!");
static_assert(SessionCache > 0 && SessionCache < 255, "There can be no less than one and no more than 255 sessions in the cache!"); static_assert(SessionCache > 0 && SessionCache < 255, "There can be no less than one and no more than 255 sessions in the cache!");
static_assert(SessionCache <= 3, "You need to decrease the size of m_iobuf in order to have more than 3 sessions at once, otherwise memory issues will occur.");
// static_assert(std::is_function<decltype(C::status)>::value, "C must have a status() function!"); // static_assert(std::is_function<decltype(C::status)>::value, "C must have a status() function!");
public: public:
@ -90,12 +91,16 @@ public:
explicit SSLClient(const C& client, const br_x509_trust_anchor *trust_anchors, const size_t trust_anchors_num, const int analog_pin, const DebugLevel debug = SSL_ERROR) explicit SSLClient(const C& client, const br_x509_trust_anchor *trust_anchors, const size_t trust_anchors_num, const int analog_pin, const DebugLevel debug = SSL_ERROR)
: SSLClientImpl(NULL, trust_anchors, trust_anchors_num, analog_pin, debug) : SSLClientImpl(NULL, trust_anchors, trust_anchors_num, analog_pin, debug)
, m_client(client) , m_client(client)
, m_sessions{} , m_sessions{SSLSession()}
, m_index(0) , m_index(0)
{ {
// for (uint8_t i = 0; i < SessionCache; i++) m_sessions[i] = SSLSession();
// since we are copying the client in the ctor, we have to set // since we are copying the client in the ctor, we have to set
// the client pointer after the class is constructed // the client pointer after the class is constructed
set_client(&m_client); set_client(&m_client);
// set the timeout to a reasonable number (it can always be changes later)
// SSL Connections take a really long time so we don't want to time out a legitimate thing
setTimeout(10 * 1000);
} }
/* /*
@ -114,23 +119,26 @@ public:
//! get the client object //! get the client object
C& getClient() { return m_client; } C& getClient() { return m_client; }
virtual SSLSession& getSession(const char* host, const IPAddress& addr) { virtual SSLSession& getSession(const char* host, const IPAddress& addr);
// search for a matching session with the IP
int temp_index = -1;
for (size_t i = 0; i < SessionCache; i++) {
// if we're looking at a real session
if (m_sessions[i].is_valid_session()
&& (
// and the hostname matches, or
(host != NULL && strcmp(host, m_sessions[i].get_hostname()) == 0)
// there is no hostname and the IP address matches
|| (host == NULL && addr == m_sessions[i].get_ip())
)) {
temp_index = i; virtual void removeSession(const char* host, const IPAddress& addr);
break;
} private:
} // utility function to find a session index based off of a host and IP
int m_getSessionIndex(const char* host, const IPAddress& addr) const;
// create a copy of the client
C m_client;
// also store an array of SSLSessions, so we can resume communication with multiple websites
SSLSession m_sessions[SessionCache];
// store an index of where a new session can be placed if we don't have any corresponding sessions
size_t m_index;
};
template <class C, size_t SessionCache>
SSLSession& SSLClient<C, SessionCache>::getSession(const char* host, const IPAddress& addr) {
const char* func_name = __func__;
// search for a matching session with the IP
int temp_index = m_getSessionIndex(host, addr);
// if none are availible, use m_index // if none are availible, use m_index
if (temp_index == -1) { if (temp_index == -1) {
temp_index = m_index; temp_index = m_index;
@ -140,18 +148,42 @@ public:
// increment m_index so the session cache is a circular buffer // increment m_index so the session cache is a circular buffer
if (temp_index == m_index && ++m_index >= SessionCache) m_index = 0; if (temp_index == m_index && ++m_index >= SessionCache) m_index = 0;
// return the pointed to value // return the pointed to value
m_info("Using session index: ", __func__); m_info("Using session index: ", func_name);
Serial.println(temp_index); Serial.println(temp_index);
return m_sessions[temp_index]; return m_sessions[temp_index];
} }
private: template <class C, size_t SessionCache>
// create a copy of the client void SSLClient<C, SessionCache>::removeSession(const char* host, const IPAddress& addr) {
C m_client; const char* func_name = __func__;
// also store an array of SSLSessions, so we can resume communication with multiple websites int temp_index = m_getSessionIndex(host, addr);
SSLSession m_sessions[SessionCache]; if (temp_index != -1) {
// store an index of where a new session can be placed if we don't have any corresponding sessions m_info(" Deleted session ", func_name);
size_t m_index; m_info(temp_index, func_name);
}; m_sessions[temp_index] = SSLSession();
}
}
template <class C, size_t SessionCache>
int SSLClient<C, SessionCache>::m_getSessionIndex(const char* host, const IPAddress& addr) const {
const char* func_name = __func__;
// search for a matching session with the IP
for (uint8_t i = 0; i < SessionCache; i++) {
// if we're looking at a real session
if (m_sessions[i].is_valid_session()
&& (
// and the hostname matches, or
(host != NULL && strcmp(host, m_sessions[i].get_hostname()) == 0)
// there is no hostname and the IP address matches
|| (host == NULL && addr == m_sessions[i].get_ip())
)) {
m_info("Found session match: ", func_name);
m_info(m_sessions[i].get_hostname(), func_name);
return i;
}
}
// none found
return -1;
}
#endif /** SSLClient_H_ */ #endif /** SSLClient_H_ */

View file

@ -45,7 +45,7 @@ SSLClientImpl::SSLClientImpl(Client *client, const br_x509_trust_anchor *trust_a
int SSLClientImpl::connect(IPAddress ip, uint16_t port) { int SSLClientImpl::connect(IPAddress ip, uint16_t port) {
const char* func_name = __func__; const char* func_name = __func__;
// connection check // connection check
if (connected()) { if (m_client->connected()) {
m_error("Cannot have two connections at the same time! Please create another SSLClient instance.", func_name); m_error("Cannot have two connections at the same time! Please create another SSLClient instance.", func_name);
return -1; return -1;
} }
@ -68,7 +68,7 @@ int SSLClientImpl::connect(IPAddress ip, uint16_t port) {
int SSLClientImpl::connect(const char *host, uint16_t port) { int SSLClientImpl::connect(const char *host, uint16_t port) {
const char* func_name = __func__; const char* func_name = __func__;
// connection check // connection check
if (connected()) { if (m_client->connected()) {
m_error("Cannot have two connections at the same time! Please create another SSLClient instance.", func_name); m_error("Cannot have two connections at the same time! Please create another SSLClient instance.", func_name);
return -1; return -1;
} }
@ -148,7 +148,7 @@ int SSLClientImpl::available() {
br_ssl_engine_recvapp_buf(&m_sslctx.eng, &alen); br_ssl_engine_recvapp_buf(&m_sslctx.eng, &alen);
return (int)(alen); return (int)(alen);
} }
else if (state == BR_SSL_CLOSED) m_warn("Engine closed after update", func_name); else if (state == BR_SSL_CLOSED) m_info("Engine closed after update", func_name);
// flush the buffer if it's stuck in the SENDAPP state // flush the buffer if it's stuck in the SENDAPP state
else if (state & BR_SSL_SENDAPP) br_ssl_engine_flush(&m_sslctx.eng, 0); else if (state & BR_SSL_SENDAPP) br_ssl_engine_flush(&m_sslctx.eng, 0);
// other state, or client is closed // other state, or client is closed
@ -194,8 +194,6 @@ void SSLClientImpl::flush() {
void SSLClientImpl::stop() { void SSLClientImpl::stop() {
// tell the SSL connection to gracefully close // tell the SSL connection to gracefully close
br_ssl_engine_close(&m_sslctx.eng); br_ssl_engine_close(&m_sslctx.eng);
// info about the socket connection
if (br_ssl_engine_current_state(&m_sslctx.eng) == BR_SSL_CLOSED) m_info("Socket was terminated before graceful closure (probably fine)", __func__);
// if the engine isn't closed, and the socket is still open // if the engine isn't closed, and the socket is still open
while (br_ssl_engine_current_state(&m_sslctx.eng) != BR_SSL_CLOSED while (br_ssl_engine_current_state(&m_sslctx.eng) != BR_SSL_CLOSED
&& m_run_until(BR_SSL_RECVAPP) == 0) { && m_run_until(BR_SSL_RECVAPP) == 0) {
@ -220,8 +218,14 @@ uint8_t SSLClientImpl::connected() {
const auto wr_ok = getWriteError() == 0; const auto wr_ok = getWriteError() == 0;
// if we're in an error state, close the connection and set a write error // if we're in an error state, close the connection and set a write error
if (br_con && !c_con) { if (br_con && !c_con) {
// If we've got a write error, the client probably failed for some reason
if (m_client->getWriteError()) {
m_error("Socket was unexpectedly interrupted. m_client error: ", func_name); m_error("Socket was unexpectedly interrupted. m_client error: ", func_name);
m_error(m_client->getWriteError(), func_name); m_error(m_client->getWriteError(), func_name);
}
// Else tell the user the endpoint closed the socket on us (ouch)
else m_warn("Socket was dropped unexpectedly (this can be an alternative to closing the connection)", func_name);
// set the write error so the engine doesn't try to close the connection
setWriteError(SSL_CLIENT_WRTIE_ERROR); setWriteError(SSL_CLIENT_WRTIE_ERROR);
stop(); stop();
} }
@ -280,11 +284,18 @@ int SSLClientImpl::m_start_ssl(const char* host, SSLSession& ssl_ses) {
m_print_br_error(br_ssl_engine_last_error(&m_sslctx.eng), SSL_ERROR); m_print_br_error(br_ssl_engine_last_error(&m_sslctx.eng), SSL_ERROR);
return 0; return 0;
} }
m_info("Connection successful!", func_name);
// all good to go! the SSL socket should be up and running // all good to go! the SSL socket should be up and running
// overwrite the session we got with new parameters // overwrite the session we got with new parameters
br_ssl_engine_get_session_parameters(&m_sslctx.eng, ssl_ses.to_br_session()); br_ssl_engine_get_session_parameters(&m_sslctx.eng, ssl_ses.to_br_session());
// set the hostname and ip in the session as well // set the hostname and ip in the session as well
ssl_ses.set_parameters(remoteIP(), host); ssl_ses.set_parameters(remoteIP(), host);
// print the handshake cipher chioce
m_info("Cipher suite: ", func_name);
if (m_debug >= SSL_INFO) {
m_print_prefix(func_name, SSL_INFO);
Serial.println(ssl_ses.cipher_suite, HEX);
}
return 1; return 1;
} }
@ -293,6 +304,7 @@ int SSLClientImpl::m_run_until(const unsigned target) {
const char* func_name = __func__; const char* func_name = __func__;
unsigned lastState = 0; unsigned lastState = 0;
size_t lastLen = 0; size_t lastLen = 0;
const unsigned long start = millis();
for (;;) { for (;;) {
unsigned state = m_update_engine(); unsigned state = m_update_engine();
// error check // error check
@ -300,11 +312,20 @@ int SSLClientImpl::m_run_until(const unsigned target) {
m_warn("Tried to run_until when the engine is closed", func_name); m_warn("Tried to run_until when the engine is closed", func_name);
return -1; return -1;
} }
// timeout check
if (millis() - start > getTimeout()) {
m_error("SSL internals timed out! This could be an internal error or bad data sent from the server", func_name);
setWriteError(SSL_BR_WRITE_ERROR);
stop();
return -1;
}
// debug // debug
if (state != lastState) { if (state != lastState) {
lastState = state; lastState = state;
m_info("m_run waiting:", func_name); m_info("m_run changed state:", func_name);
printState(state); printState(state);
m_info("Memory: ", func_name);
m_info(freeMemory(), func_name);
} }
if (state & BR_SSL_RECVREC) { if (state & BR_SSL_RECVREC) {
size_t len; size_t len;
@ -455,7 +476,8 @@ unsigned SSLClientImpl::m_update_engine() {
m_info("Read bytes from client: ", func_name); m_info("Read bytes from client: ", func_name);
m_info(avail, func_name); m_info(avail, func_name);
m_info(len, func_name); m_info(len, func_name);
m_info("Memory: ", func_name);
m_info(freeMemory(), func_name);
// I suppose so! // I suppose so!
int rlen = m_client->read(buf, len); int rlen = m_client->read(buf, len);
if (rlen <= 0) { if (rlen <= 0) {
@ -495,20 +517,20 @@ void SSLClientImpl::m_print_prefix(const char* func_name, const DebugLevel level
Serial.print("(SSLClient)"); Serial.print("(SSLClient)");
// print the debug level // print the debug level
switch (level) { switch (level) {
case SSL_INFO: Serial.print("SSL_INFO"); break; case SSL_INFO: Serial.print("(SSL_INFO)"); break;
case SSL_WARN: Serial.print("SSL_WARN"); break; case SSL_WARN: Serial.print("(SSL_WARN)"); break;
case SSL_ERROR: Serial.print("SSL_ERROR"); break; case SSL_ERROR: Serial.print("(SSL_ERROR)"); break;
default: Serial.print("Unknown level"); default: Serial.print("(Unknown level)");
} }
// print the function name // print the function name
Serial.print("(");
Serial.print(func_name); Serial.print(func_name);
// get ready Serial.print("): ");
Serial.print(": ");
} }
/** See SSLClientImpl.h */ /** See SSLClientImpl.h */
void SSLClientImpl::m_print_ssl_error(const int ssl_error, const DebugLevel level) const { void SSLClientImpl::m_print_ssl_error(const int ssl_error, const DebugLevel level) const {
if (level < m_debug) return; if (level > m_debug) return;
m_print_prefix(__func__, level); m_print_prefix(__func__, level);
switch(ssl_error) { switch(ssl_error) {
case SSL_OK: Serial.println("SSL_OK"); break; case SSL_OK: Serial.println("SSL_OK"); break;
@ -522,7 +544,7 @@ void SSLClientImpl::m_print_ssl_error(const int ssl_error, const DebugLevel leve
/* See SSLClientImpl.h */ /* See SSLClientImpl.h */
void SSLClientImpl::m_print_br_error(const unsigned br_error_code, const DebugLevel level) const { void SSLClientImpl::m_print_br_error(const unsigned br_error_code, const DebugLevel level) const {
if (level < m_debug) return; if (level > m_debug) return;
m_print_prefix(__func__, level); m_print_prefix(__func__, level);
switch (br_error_code) { switch (br_error_code) {
case BR_ERR_BAD_PARAM: Serial.println("Caller-provided parameter is incorrect."); break; case BR_ERR_BAD_PARAM: Serial.println("Caller-provided parameter is incorrect."); break;

View file

@ -45,11 +45,31 @@ enum Error {
*/ */
enum DebugLevel { enum DebugLevel {
SSL_NONE = 0, SSL_NONE = 0,
SSL_INFO = 1, SSL_ERROR = 1,
SSL_WARN = 2, SSL_WARN = 2,
SSL_ERROR = 3 SSL_INFO = 3,
}; };
#ifdef __arm__
// should use uinstd.h to define sbrk but Due causes a conflict
extern "C" char* sbrk(int incr);
#else // __ARM__
extern char *__brkval;
#endif // __arm__
static int freeMemory() {
char top;
#ifdef __arm__
return &top - reinterpret_cast<char*>(sbrk(0));
#elif defined(CORE_TEENSY) || (ARDUINO > 103 && ARDUINO != 151)
return &top - __brkval;
#else // __arm__
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
#endif // __arm__
}
/** TODO: Write what this is */ /** TODO: Write what this is */
class SSLClientImpl : public Client { class SSLClientImpl : public Client {
@ -170,7 +190,7 @@ protected:
template<typename T> template<typename T>
void m_print(const T str, const char* func_name, const DebugLevel level) const { void m_print(const T str, const char* func_name, const DebugLevel level) const {
// check the current debug level // check the current debug level
if (level < m_debug) return; if (level > m_debug) return;
// print prefix // print prefix
m_print_prefix(func_name, level); m_print_prefix(func_name, level);
// print the message // print the message
@ -226,7 +246,9 @@ private:
// can expand to a bi-directional buffer with maximum of BR_SSL_BUFSIZE_BIDI // can expand to a bi-directional buffer with maximum of BR_SSL_BUFSIZE_BIDI
// or shrink to below BR_SSL_BUFSIZE_MONO, and bearSSL will adapt automatically // or shrink to below BR_SSL_BUFSIZE_MONO, and bearSSL will adapt automatically
// simply edit this value to change the buffer size to the desired value // simply edit this value to change the buffer size to the desired value
unsigned char m_iobuf[BR_SSL_BUFSIZE_MONO]; // additionally, we need to correct buffer size based off of how many sessions we decide to cache
// since SSL takes so much memory if we don't it will cause the stack and heap to collide
unsigned char m_iobuf[BR_SSL_BUFSIZE_MONO / 4];
static_assert(sizeof m_iobuf <= BR_SSL_BUFSIZE_BIDI, "m_iobuf must be below maximum buffer size"); static_assert(sizeof m_iobuf <= BR_SSL_BUFSIZE_BIDI, "m_iobuf must be below maximum buffer size");
// store the index of where we are writing in the buffer // store the index of where we are writing in the buffer
// so we can send our records all at once to prevent // so we can send our records all at once to prevent

View file

@ -418,6 +418,7 @@ br_client_init_TLS12_only(br_ssl_client_context *cc,
* supported hash function is appropriate; here we use SHA-256. * supported hash function is appropriate; here we use SHA-256.
* The trust an * The trust an
*/ */
memset(xc, 0, sizeof *xc);
br_x509_minimal_init(xc, &br_sha256_vtable, br_x509_minimal_init(xc, &br_sha256_vtable,
trust_anchors, trust_anchors_num); trust_anchors, trust_anchors_num);

View file

@ -119,6 +119,7 @@ br_ssl_client_init_full(br_ssl_client_context *cc,
* to TLS-1.2 (inclusive). * to TLS-1.2 (inclusive).
*/ */
br_ssl_client_zero(cc); br_ssl_client_zero(cc);
memset(xc, 0, sizeof *xc);
br_ssl_engine_set_versions(&cc->eng, BR_TLS10, BR_TLS12); br_ssl_engine_set_versions(&cc->eng, BR_TLS10, BR_TLS12);
/* /*