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;
}
}