diff --git a/src/SSLClient.cpp b/src/SSLClient.cpp index 045bfda..d4c2981 100644 --- a/src/SSLClient.cpp +++ b/src/SSLClient.cpp @@ -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; + } } \ No newline at end of file diff --git a/src/SSLClient.h b/src/SSLClient.h index d14135d..9907aa5 100644 --- a/src/SSLClient.h +++ b/src/SSLClient.h @@ -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; };