refactored client to use the generic IO API for bearSSL, need to figure out a way to structure write() such that bytes are only written when availible() is called, to allow for multiple calls without significant delay and/or bugs

This commit is contained in:
Noah Laptop 2019-02-25 18:18:41 -08:00
parent 093d1fac8b
commit f4ea538dab
2 changed files with 193 additions and 16 deletions

View file

@ -24,32 +24,36 @@
SSLClient::SSLClient(const C &client, const br_x509_trust_anchor *trust_anchors, const size_t trust_anchors_num, const bool debug) {
// initlalize the various bearssl libraries so they're ready to go when we connect
br_client_init_TLS12_only(&m_sslctx, &m_x509ctx, m_trust_anchors, m_trust_anchors_num);
br_ssl_engine_set_buffer(&m_sslctx, m_iobuf, sizeof m_iobuf, 0);
// check if the buffer size is half or full duplex
constexpr auto duplex = sizeof iobuf <= BR_SSL_BUFSIZE_MONO ? 0 : 1;
br_ssl_engine_set_buffer(&m_sslctx, m_iobuf, sizeof m_iobuf, duplex);
br_sslio_init(&m_ioctx, &m_sslctx.eng, m_readraw, NULL, m_writeraw, NULL);
}
/* see SSLClient.h */
virtual int SSLClient::connect(IPAddress ip, uint16_t port) {
// Warning for security
m_print("Warning! Using a raw IP Address for an SSL connection bypasses some important verification steps\nYou should use a domain name (www.google.com) whenever possible.")
m_print("Warning! Using a raw IP Address for an SSL connection bypasses some important verification steps\nYou should use a domain name (www.google.com) whenever possible.");
// first we need our hidden client member to negotiate the socket for us,
// since most times socket functionality is implemented in hardeware.
if (!this->m_client.connect(ip, port)) {
m_print("Failed to connect using m_client");
setWriteError(SSL_CLIENT_CONNECT_FAIL);
return 0;
}
// reset the client context, and look for previous sessions
// in this case we also provide NULL host since we only have an IP
br_ssl_client_reset(&sc, NULL, 1);
// initlalize the SSL socket over the network
// normally this would happen in br_sslio_write, but I think it makes
// a little more structural sense to put it here
if (br_run_until(ctx, BR_SSL_SENDAPP) < 0) {
if (m_run_until(ctx, BR_SSL_SENDAPP) < 0) {
m_print("Failed to initlalize the SSL layer");
setWriteError(SSL_BR_CONNECT_FAIL);
return 0;
}
// all good to go! the SSL socket should be up and running
m_print("SSL Initialized");
setWriteError(SSL_OK);
return 1;
}
@ -59,6 +63,7 @@ virtual int SSLClient::connect(const char *host, uint16_t port) {
// since most times socket functionality is implemented in hardeware.
if (!this->m_client.connect(host, port)) {
m_print("Failed to connect using m_client");
setWriteError(SSL_CLIENT_CONNECT_FAIL);
return 0;
}
// reset the client context, and look for previous sessions
@ -66,19 +71,164 @@ virtual int SSLClient::connect(const char *host, uint16_t port) {
// initlalize the SSL socket over the network
// normally this would happen in br_sslio_write, but I think it makes
// a little more structural sense to put it here
if (br_run_until(ctx, BR_SSL_SENDAPP) < 0) {
if (m_run_until(BR_SSL_SENDAPP) < 0) {
m_print("Failed to initlalize the SSL layer");
setWriteError(SSL_BR_CONNECT_FAIL);
return 0;
}
// all good to go! the SSL socket should be up and running
m_print("SSL Initialized");
setWriteError(SSL_OK);
return 1;
}
/** see SSLClient.h TODO: fix */
virtual size_t SSLClient::write(const uint8_t *buf, size_t size) {
// check if the socket is still open and such
if(!m_client.connected()) {
m_print("Client is not connected! Perhaps something has happened?");
setWriteError(SSL_CLIENT_CONNECT_FAIL);
return 0;
}
// write to the ssl socket using bearssl, and error check
int status = br_sslio_write_all(&m_ioctx, buf, len);
if (status < 0 )
const auto status = br_sslio_write_all(&m_ioctx, buf, size);
if (status != size) {
if (status < 0) {
m_print("Encountered a write error:");
if (m_client.getWriteError()) {
m_print("m_client write error");
setWriteError(SSL_CLIENT_WRTIE_ERROR);
}
else {
m_print("bearssl write error: ");
m_print(err = br_ssl_engine_last_error(&m_sslctx.eng));
setWriteError(SSL_BR_WRITE_ERROR);
}
return 0;
}
m_print("Warn: Wrote less than status! Something might be wrong");
}
return status;
}
virtual int SSLClient::available() {
if (!m_client.connected()) {
m_print("Cannot check available of disconnected client!");
return 0;
}
// run the SSL engine until we are waiting for either user input or a server response
unsigned state = m_update_engine();
if(state & BR_SSL_RECVAPP) {
// return how many received bytes we have
size_t alen;
br_ssl_engine_recvapp_buf(ctx->engine, &alen);
return (int)alen;
}
else if (state == BR_SSL_CLOSED) m_print("Tried to check available when engine is closed!");
// flush the buffer if it's stuck in the SENDAPP state
else if (state & BR_SSL_SENDAPP) br_ssl_engine_flush(m_sslctx->engine, 0);
else if (state == 0) {
m_print("SSL engine failed: ");
m_print(br_ssl_engine_last_error(&m_sslctx));
setWriteError(SSL_BR_WRITE_ERROR);
}
// other state, or client is closed
return 0;
}
int SSLClient::m_run_until(const unsigned target) {
for (;;) {
unsigned state = m_update_engine();
/*
* If we reached our target, then we are finished.
*/
if (state & target) return 0;
/*
* If some application data must be read, and we did not
* exit, then this means that we are trying to write data,
* and that's not possible until the application data is
* read. This may happen if using a shared in/out buffer,
* and the underlying protocol is not strictly half-duplex.
* This is unrecoverable here, so we report an error.
*/
if (state & BR_SSL_RECVAPP && target & BR_SSL_SENDAPP) return -1;
/*
* We can reach that point if the target RECVAPP, and
* the state contains SENDAPP only. This may happen with
* a shared in/out buffer. In that case, we must flush
* the buffered data to "make room" for a new incoming
* record.
*/
if (state & SENDAPP && target & RECVAPP) br_ssl_engine_flush(m_sslctx->engine, 0);
}
}
unsigned SSLClient::m_update_engine() {
for(;;) {
// get the state
unsigned state = br_ssl_engine_current_state(m_sslctx->engine);
if (state & BR_SSL_CLOSED) return state;
/*
* If there is some record data to send, do it. This takes
* precedence over everything else.
*/
if (state & BR_SSL_SENDREC) {
unsigned char *buf;
size_t len;
int wlen;
buf = br_ssl_engine_sendrec_buf(ctx->engine, &len);
wlen = m_client.write(buf, len);
if (wlen < 0) {
/*
* If we received a close_notify and we
* still send something, then we have our
* own response close_notify to send, and
* the peer is allowed by RFC 5246 not to
* wait for it.
*/
if (!ctx->engine->shutdown_recv) {
br_ssl_engine_fail(
ctx->engine, BR_ERR_IO);
}
setWriteError(SSL_BR_WRITE_ERROR);
return 0;
}
if (wlen > 0) {
br_ssl_engine_sendrec_ack(ctx->engine, wlen);
}
continue;
}
/*
* If there is some record data to recieve, check if we've
* recieved it so far. If we have, then we can update the state.
* else we can return that we're still waiting for the server.
*/
if (state & BR_SSL_RECVREC) {
size_t len;
unsigned char * buf = br_ssl_engine_recvrec_buf(ctx->engine, &len);
// do we have the record you're looking for?
if (m_client.available() >= len) {
// I suppose so!
int rlen = m_client.readBytes((char *)buf, len);
if (rlen < 0) {
br_ssl_engine_fail(ctx->engine, BR_ERR_IO);
setWriteError(SSL_BR_WRITE_ERROR);
return 0;
}
if (rlen > 0) {
br_ssl_engine_recvrec_ack(ctx->engine, rlen);
}
continue;
}
// guess not, tell the state we're waiting still
else return state;
}
// if it's not any of the above states, then it must be waiting to send or recieve app data
// in which case we return
return state;
}
}

View file

@ -49,19 +49,35 @@ class SSLClient : public Client {
static_assert(std::is_base_of(Client, C)::value, "C must be a Client Class!");
static_assert(std::is_function(decltype(C::status))::value, "C must have a status() function!");
/** error enums
* Static constants defining the possible errors encountered
* Read from getWriteError();
*/
enum Error {
SSL_OK = 0,
SSL_CLIENT_CONNECT_FAIL,
SSL_BR_CONNECT_FAIL,
SSL_CLIENT_WRTIE_ERROR,
SSL_BR_WRITE_ERROR,
};
public:
/**
* @brief copies the client object and initializes SSL contexts for bearSSL
*
* We copy the client because we aren't sure the Client object
* is going to exists past the inital creation of the SSLClient.
* @param client the (Ethernet)client object
*
* @pre The client class must be able to access the internet, as SSLClient
* cannot manage this for you.
*
* @param trust_anchors Trust anchors used in the verification
* of the SSL server certificate, generated using the `brssl` command
* line utility. For more information see the samples or bearssl.org
* @param trust_anchors_num The number of trust anchors stored
* @param debug whether to enable or disable debug logging, must be constexpr
*/
SSLClient(const C &client, const br_x509_trust_anchor *trust_anchors, const size_t trust_anchors_num, const bool debug = true)
SSLClient(const C client, const br_x509_trust_anchor *trust_anchors, const size_t trust_anchors_num, const bool debug = true)
: m_client(client)
, m_trust_anchors(trust_anchors)
, m_trust_anchors_num(trust_anchors_num)
@ -107,6 +123,9 @@ public:
* @param ip The ip address to connect to
* @param port the port to connect to
* @returns 1 if success, 0 if failure (as found in EthernetClient)
*
* @error SSL_CLIENT_CONNECT_FAIL The client object could not connect to the host or port
* @error SSL_BR_CONNECT_FAIL BearSSL could not initialize the SSL connection.
*/
virtual int connect(IPAddress ip, uint16_t port = 443);
/**
@ -128,6 +147,9 @@ public:
* @param host The cstring host ("www.google.com")
* @param port the port to connect to
* @returns 1 of success, 0 if failure (as found in EthernetClient)
*
* @error SSL_CLIENT_CONNECT_FAIL The client object could not connect to the host or port
* @error SSL_BR_CONNECT_FAIL BearSSL could not initialize the SSL connection.
*/
virtual int connect(const char *host, uint16_t port = 443);
virtual size_t write(uint8_t b) { return write(&b, 1); }
@ -140,7 +162,7 @@ public:
virtual void stop();
virtual uint8_t connected();
/** get the client object */
//! get the client object
C& getClient() { return m_client; }
private:
@ -152,12 +174,12 @@ private:
Serial.println(str);
}
}
/** Callback function pointing to m_client.read to be used by the br_sslio API */
int m_readraw(void *ctx, unsigned char *buf, size_t len);
/** Callback function pointing to m_client.write to be used by the br_sslio API */
int m_writeraw(void *ctx, unsigned char *buf, size_t len);
/** run the bearssl engine until a certain state */
int m_run_until(const unsigned target);
/** proxy for availble that returns the state */
int m_update_engine();
// create a copy of the client
C m_client;
const C m_client;
// store pointers to the trust anchors
// should not be computed at runtime
constexpr br_x509_trust_anchor *m_trust_anchors;
@ -167,7 +189,12 @@ private:
// store the context values required for SSL
br_ssl_client_context m_sslctx;
br_x509_minimal_context m_x509ctx;
// use a mono-directional buffer by default to cut memory in half
// 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
// simply edit this value to change the buffer size to the desired value
unsigned char m_iobuf[BR_SSL_BUFSIZE_MONO];
static_assert(sizeof m_iobuf <= BR_SSL_BUFSIZE_BIDI);
br_sslio_context m_ioctx;
};