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:
parent
093d1fac8b
commit
f4ea538dab
2 changed files with 193 additions and 16 deletions
|
@ -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) {
|
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
|
// 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_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);
|
br_sslio_init(&m_ioctx, &m_sslctx.eng, m_readraw, NULL, m_writeraw, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* see SSLClient.h */
|
/* see SSLClient.h */
|
||||||
virtual int SSLClient::connect(IPAddress ip, uint16_t port) {
|
virtual int SSLClient::connect(IPAddress ip, uint16_t port) {
|
||||||
// Warning for security
|
// 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,
|
// first we need our hidden client member to negotiate the socket for us,
|
||||||
// since most times socket functionality is implemented in hardeware.
|
// since most times socket functionality is implemented in hardeware.
|
||||||
if (!this->m_client.connect(ip, port)) {
|
if (!this->m_client.connect(ip, port)) {
|
||||||
m_print("Failed to connect using m_client");
|
m_print("Failed to connect using m_client");
|
||||||
|
setWriteError(SSL_CLIENT_CONNECT_FAIL);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// reset the client context, and look for previous sessions
|
// 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);
|
br_ssl_client_reset(&sc, NULL, 1);
|
||||||
// initlalize the SSL socket over the network
|
// initlalize the SSL socket over the network
|
||||||
// normally this would happen in br_sslio_write, but I think it makes
|
// normally this would happen in br_sslio_write, but I think it makes
|
||||||
// a little more structural sense to put it here
|
// 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");
|
m_print("Failed to initlalize the SSL layer");
|
||||||
|
setWriteError(SSL_BR_CONNECT_FAIL);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// all good to go! the SSL socket should be up and running
|
// all good to go! the SSL socket should be up and running
|
||||||
m_print("SSL Initialized");
|
m_print("SSL Initialized");
|
||||||
|
setWriteError(SSL_OK);
|
||||||
return 1;
|
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.
|
// since most times socket functionality is implemented in hardeware.
|
||||||
if (!this->m_client.connect(host, port)) {
|
if (!this->m_client.connect(host, port)) {
|
||||||
m_print("Failed to connect using m_client");
|
m_print("Failed to connect using m_client");
|
||||||
|
setWriteError(SSL_CLIENT_CONNECT_FAIL);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// reset the client context, and look for previous sessions
|
// 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
|
// initlalize the SSL socket over the network
|
||||||
// normally this would happen in br_sslio_write, but I think it makes
|
// normally this would happen in br_sslio_write, but I think it makes
|
||||||
// a little more structural sense to put it here
|
// 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");
|
m_print("Failed to initlalize the SSL layer");
|
||||||
|
setWriteError(SSL_BR_CONNECT_FAIL);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// all good to go! the SSL socket should be up and running
|
// all good to go! the SSL socket should be up and running
|
||||||
m_print("SSL Initialized");
|
m_print("SSL Initialized");
|
||||||
|
setWriteError(SSL_OK);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** see SSLClient.h TODO: fix */
|
||||||
virtual size_t SSLClient::write(const uint8_t *buf, size_t size) {
|
virtual size_t SSLClient::write(const uint8_t *buf, size_t size) {
|
||||||
// check if the socket is still open and such
|
// 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
|
// write to the ssl socket using bearssl, and error check
|
||||||
int status = br_sslio_write_all(&m_ioctx, buf, len);
|
const auto status = br_sslio_write_all(&m_ioctx, buf, size);
|
||||||
if (status < 0 )
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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_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!");
|
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:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief copies the client object and initializes SSL contexts for bearSSL
|
* @brief copies the client object and initializes SSL contexts for bearSSL
|
||||||
|
*
|
||||||
* We copy the client because we aren't sure the Client object
|
* We copy the client because we aren't sure the Client object
|
||||||
* is going to exists past the inital creation of the SSLClient.
|
* 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
|
* @param trust_anchors Trust anchors used in the verification
|
||||||
* of the SSL server certificate, generated using the `brssl` command
|
* of the SSL server certificate, generated using the `brssl` command
|
||||||
* line utility. For more information see the samples or bearssl.org
|
* line utility. For more information see the samples or bearssl.org
|
||||||
* @param trust_anchors_num The number of trust anchors stored
|
* @param trust_anchors_num The number of trust anchors stored
|
||||||
* @param debug whether to enable or disable debug logging, must be constexpr
|
* @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_client(client)
|
||||||
, m_trust_anchors(trust_anchors)
|
, m_trust_anchors(trust_anchors)
|
||||||
, m_trust_anchors_num(trust_anchors_num)
|
, m_trust_anchors_num(trust_anchors_num)
|
||||||
|
@ -107,6 +123,9 @@ public:
|
||||||
* @param ip The ip address to connect to
|
* @param ip The ip address to connect to
|
||||||
* @param port the port to connect to
|
* @param port the port to connect to
|
||||||
* @returns 1 if success, 0 if failure (as found in EthernetClient)
|
* @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);
|
virtual int connect(IPAddress ip, uint16_t port = 443);
|
||||||
/**
|
/**
|
||||||
|
@ -128,6 +147,9 @@ public:
|
||||||
* @param host The cstring host ("www.google.com")
|
* @param host The cstring host ("www.google.com")
|
||||||
* @param port the port to connect to
|
* @param port the port to connect to
|
||||||
* @returns 1 of success, 0 if failure (as found in EthernetClient)
|
* @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 int connect(const char *host, uint16_t port = 443);
|
||||||
virtual size_t write(uint8_t b) { return write(&b, 1); }
|
virtual size_t write(uint8_t b) { return write(&b, 1); }
|
||||||
|
@ -140,7 +162,7 @@ public:
|
||||||
virtual void stop();
|
virtual void stop();
|
||||||
virtual uint8_t connected();
|
virtual uint8_t connected();
|
||||||
|
|
||||||
/** get the client object */
|
//! get the client object
|
||||||
C& getClient() { return m_client; }
|
C& getClient() { return m_client; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -152,12 +174,12 @@ private:
|
||||||
Serial.println(str);
|
Serial.println(str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/** Callback function pointing to m_client.read to be used by the br_sslio API */
|
/** run the bearssl engine until a certain state */
|
||||||
int m_readraw(void *ctx, unsigned char *buf, size_t len);
|
int m_run_until(const unsigned target);
|
||||||
/** Callback function pointing to m_client.write to be used by the br_sslio API */
|
/** proxy for availble that returns the state */
|
||||||
int m_writeraw(void *ctx, unsigned char *buf, size_t len);
|
int m_update_engine();
|
||||||
// create a copy of the client
|
// create a copy of the client
|
||||||
C m_client;
|
const C m_client;
|
||||||
// store pointers to the trust anchors
|
// store pointers to the trust anchors
|
||||||
// should not be computed at runtime
|
// should not be computed at runtime
|
||||||
constexpr br_x509_trust_anchor *m_trust_anchors;
|
constexpr br_x509_trust_anchor *m_trust_anchors;
|
||||||
|
@ -167,7 +189,12 @@ private:
|
||||||
// store the context values required for SSL
|
// store the context values required for SSL
|
||||||
br_ssl_client_context m_sslctx;
|
br_ssl_client_context m_sslctx;
|
||||||
br_x509_minimal_context m_x509ctx;
|
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];
|
unsigned char m_iobuf[BR_SSL_BUFSIZE_MONO];
|
||||||
|
static_assert(sizeof m_iobuf <= BR_SSL_BUFSIZE_BIDI);
|
||||||
br_sslio_context m_ioctx;
|
br_sslio_context m_ioctx;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue