fixed syntax errors, switched internal Client to pointers, and finally got it to compile!

This commit is contained in:
Noah Laptop 2019-02-27 17:22:42 -08:00
parent 5a0debb2fb
commit 797a3cdf94
4 changed files with 212 additions and 163 deletions

View file

@ -36,29 +36,16 @@
*/
#include <type_traits>
#include "bearssl.h"
#include "Arduino.h"
#include "Client.h"
#include "SSLClientImpl.h"
#ifndef SSLClient_H_
#define SSLClient_H_
template <class C>
class SSLClient : public Client {
/** static type checks
* I'm a java developer, so I want to ensure that my inheritance is safe.
* These checks ensure that all the functions we use on class C are
* actually present on class C. It does this by first checking that the
* class inherits from Client, and then that it contains a status() function.
*/
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,
@ -68,9 +55,35 @@ enum Error {
SSL_INTERNAL_ERROR
};
/**
* \brief This class serves as a templating proxy class for the SSLClientImpl to do the real work.
*
* A problem arose when writing this class: I wanted the user to be able to construct
* this class in a single line of code (e.g. SSLClient(EthernetClient())), but I also
* wanted to avoid the use of dynamic memory if possible. In an attempt to solve this
* problem I used a templated classes. However, becuase of the Arduino build process
* this meant that the implementations for all the functions had to be in a header
* file (a weird effect of using templated classes and linking) which would slow down
* the build quite a bit. As a comprimise, I instead decided to build the main class (SSLCLient)
* as a templated class, and have use a not templated implementation class (SSLClientImpl)
* that would be able to reside in a seperate file. This gets the best of both worlds
* from the client side, however from the developer side it can be a bit confusing.
*/
template <class C>
class SSLClient : public SSLClientImpl {
/** static type checks
* I'm a java developer, so I want to ensure that my inheritance is safe.
* These checks ensure that all the functions we use on class C are
* actually present on class C. It does this by first checking that the
* class inherits from Client, and then that it contains a status() function.
*/
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!");
public:
/**
* @brief copies the client object and initializes SSL contexts for bearSSL
* @brief copies the client object, and passes the various parameters to the SSLCLientImpl functions.
*
* We copy the client because we aren't sure the Client object
* is going to exists past the inital creation of the SSLClient.
@ -84,123 +97,36 @@ public:
* @param trust_anchors_num The number of trust anchors stored
* @param debug whether to enable or disable debug logging, must be constexpr
*/
explicit SSLClient(const C &client, const br_x509_trust_anchor *trust_anchors, const size_t trust_anchors_num, const bool debug = true);
/** Dtor is implicit since unique_ptr handles it fine */
SSLClient(const C& client, const br_x509_trust_anchor *trust_anchors, const size_t trust_anchors_num, const bool debug = true)
: SSLClientImpl(NULL, trust_anchors, trust_anchors_num, debug)
, m_client(client)
{
// since we are copying the client in the ctor, we have to set
// the client pointer after the class is constructed
set_client(&m_client);
}
/**
* The virtual functions defining a Client are below
* The special functions most clients have are below
* Most of them smply pass through
*/
virtual int availableForWrite(void) { return m_client.availableForWrite(); };
virtual operator bool() { return static_cast<bool>(m_client); }
// virtual bool operator==(const bool value) { return bool() == value; }
// virtual bool operator!=(const bool value) { return bool() != value; }
// virtual bool operator==(const C& rhs) const { return m_client.operator==(rhs); }
// virtual bool operator!=(const C& rhs) const { return !this->operator==(rhs); }
virtual int availableForWrite(void) { return m_client.availableForWrite(); }
virtual operator bool() { return connected() > 0; }
virtual bool operator==(const bool value) { return bool() == value; }
virtual bool operator!=(const bool value) { return bool() != value; }
virtual bool operator==(const C& rhs) { return m_client == rhs; }
virtual bool operator!=(const C& rhs) { return m_client != rhs; }
virtual uint16_t localPort() { return m_client.localPort(); }
virtual IPAddress remoteIP() { return m_client.remoteIP(); }
virtual uint16_t remotePort() { return m_client.remotePort(); }
virtual void setConnectionTimeout(uint16_t timeout) { m_client.setConnectionTimeout(timeout); }
/** functions specific to the EthernetClient which I'll have to override */
// uint8_t status();
// uint8_t getSocketNumber() const;
/** functions dealing with read/write that BearSSL will be injected into */
/**
* @brief Connect over SSL to a host specified by an ip address
*
* SSLClient::connect(host, port) should be preffered over this function,
* as verifying the domain name is a step in ensuring the certificate is
* legitimate, which is important to the security of the device. Additionally,
* SSL sessions cannot be resumed, which can drastically increase initial
* connect time.
*
* This function initializes EthernetClient by calling EthernetClient::connect
* with the parameters supplied, then once the socket is open initializes
* the appropriete bearssl contexts using the TLS_only_profile. Due to the
* design of the SSL standard, this function will probably take an extended
* period (1-2sec) to negotiate the handshake and finish the connection.
*
* @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);
/**
* @brief Connect over SSL using connect(ip, port), but use a DNS lookup to
* get the IP Address first.
*
* This function initializes EthernetClient by calling EthernetClient::connect
* with the parameters supplied, then once the socket is open initializes
* the appropriete bearssl contexts using the TLS_only_profile.
*
* Due to the design of the SSL standard, this function will probably take an
* extended period (1-2sec) to negotiate the handshake and finish the
* connection. Since the hostname is provided, however, BearSSL is able to keep
* a session cache of the clients we have connected to. This should reduce
* connection time greatly. In order to use this feature, you must reuse the
* same SSLClient object to connect to the reused host. Doing this will allow
* BearSSL to automatically match the hostname to a cached session.
*
* @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);
virtual size_t write(uint8_t b) { return write(&b, 1); }
virtual size_t write(const uint8_t *buf, size_t size);
virtual int available();
virtual int read() { int peeked = peek(); if(peeked != -1) br_ssl_engine_recvapp_ack(&m_sslctx.eng, 1); return peeked; }
virtual int read(uint8_t *buf, size_t size);
virtual int peek();
virtual void flush();
virtual void stop();
virtual uint8_t connected();
//! get the client object
C& getClient() { return m_client; }
private:
/** @brief debugging print function, only prints if m_debug is true */
template<typename T>
constexpr void m_print(const T str) const {
if (m_debug) {
Serial.print("SSLClient: ");
Serial.println(str);
}
}
/** run the bearssl engine until a certain state */
int m_run_until(const unsigned target);
/** proxy for availble that returns the state */
unsigned m_update_engine();
// create a copy of the client
C m_client;
// store pointers to the trust anchors
// should not be computed at runtime
const br_x509_trust_anchor *m_trust_anchors;
const size_t m_trust_anchors_num;
// store whether to enable debug logging
const bool m_debug;
// 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, "m_iobuf must be below maximum buffer size");
// store the index of where we are writing in the buffer
// so we can send our records all at once to prevent
// weird timing issues
size_t m_write_idx;
};
#endif /** SSLClient_H_ */

View file

@ -20,9 +20,8 @@
#include "SSLClient.h"
/** see SSLClient.h */
template<class C>
SSLClient<C>::SSLClient(const C &client, const br_x509_trust_anchor *trust_anchors, const size_t trust_anchors_num, const bool debug)
/** see SSLClientImpl.h */
SSLClientImpl::SSLClientImpl(Client* client, const br_x509_trust_anchor *trust_anchors, const size_t trust_anchors_num, const bool debug)
: m_client(client)
, m_trust_anchors(trust_anchors)
, m_trust_anchors_num(trust_anchors_num)
@ -38,16 +37,15 @@ SSLClient<C>::SSLClient(const C &client, const br_x509_trust_anchor *trust_ancho
br_ssl_engine_set_buffer(&m_sslctx.eng, m_iobuf, sizeof m_iobuf, duplex);
}
/* see SSLClient.h */
template<class C>
int SSLClient<C>::connect(IPAddress ip, uint16_t port) {
/* see SSLClientImpl.h*/
int SSLClientImpl::connect(IPAddress ip, uint16_t port) {
// reset indexs for saftey
m_write_idx = 0;
// 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.");
// 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)) {
if (!m_client->connect(ip, port)) {
m_print("Failed to connect using m_client");
setWriteError(SSL_CLIENT_CONNECT_FAIL);
return 0;
@ -68,15 +66,13 @@ int SSLClient<C>::connect(IPAddress ip, uint16_t port) {
return 1;
}
/* see SSLClient.h */
template<class C>
int SSLClient<C>::connect(const char *host, uint16_t port) {
/* see SSLClientImpl.h*/
int SSLClientImpl::connect(const char *host, uint16_t port) {
// reset indexs for saftey
m_write_idx = 0;
// 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(host, port)) {
if (!m_client->connect(host, port)) {
m_print("Failed to connect using m_client");
setWriteError(SSL_CLIENT_CONNECT_FAIL);
return 0;
@ -97,9 +93,8 @@ int SSLClient<C>::connect(const char *host, uint16_t port) {
return 1;
}
/** see SSLClient.h */
template<class C>
size_t SSLClient<C>::write(const uint8_t *buf, size_t size) {
/** see SSLClientImpl.h*/
size_t SSLClientImpl::write(const uint8_t *buf, size_t size) {
// check if the socket is still open and such
if(!connected()) {
m_print("Client is not connected! Perhaps something has happened?");
@ -143,9 +138,8 @@ size_t SSLClient<C>::write(const uint8_t *buf, size_t size) {
return size;
}
/** see SSLClient.h */
template<class C>
int SSLClient<C>::available() {
/** see SSLClientImpl.h*/
int SSLClientImpl::available() {
// connection check
if (!connected()) {
m_print("Warn: Cannot check available of disconnected client");
@ -171,9 +165,8 @@ int SSLClient<C>::available() {
return 0;
}
/** see SSLClient.h */
template<class C>
int SSLClient<C>::read(uint8_t *buf, size_t size) {
/** see SSLClientImpl.h */
int SSLClientImpl::read(uint8_t *buf, size_t size) {
// check that the engine is ready to read
if (available() <= 0) return -1;
// read the buffer, send the ack, and return the bytes read
@ -187,9 +180,8 @@ int SSLClient<C>::read(uint8_t *buf, size_t size) {
return read_amount;
}
/** see SSLClient.h */
template<class C>
int SSLClient<C>::peek() {
/** see SSLClientImpl.h */
int SSLClientImpl::peek() {
// check that the engine is ready to read
if (available() <= 0) return -1;
// read the buffer, send the ack, and return the bytes read
@ -200,18 +192,16 @@ int SSLClient<C>::peek() {
return (int)read_num;
}
/** see SSLClient.h */
template<class C>
void SSLClient<C>::flush() {
/** see SSLClientImpl.h*/
void SSLClientImpl::flush() {
// trigger a flush, incase there's any leftover data
br_ssl_engine_flush(&m_sslctx.eng, 0);
// run until application data is ready for pickup
if(m_run_until(BR_SSL_RECVAPP) < 0) m_print("Error: could not flush write buffer!");
}
/** see SSLClient.h */
template<class C>
void SSLClient<C>::stop() {
/** see SSLClientImpl.h*/
void SSLClientImpl::stop() {
// tell the SSL connection to gracefully close
br_ssl_engine_close(&m_sslctx.eng);
while (br_ssl_engine_current_state(&m_sslctx.eng) != BR_SSL_CLOSED) {
@ -226,29 +216,27 @@ void SSLClient<C>::stop() {
}
}
// close the ethernet socket
m_client.stop();
m_client->stop();
}
template<class C>
uint8_t SSLClient<C>::connected() {
uint8_t SSLClientImpl::connected() {
// check all of the error cases
const auto c_con = m_client.connected();
const auto c_con = m_client->connected();
const auto br_con = br_ssl_engine_current_state(&m_sslctx.eng) != BR_SSL_CLOSED;
const auto wr_ok = getWriteError() == 0;
// if we're in an error state, close the connection and set a write error
if ((br_con && !c_con) || !wr_ok) {
m_print("Error: Socket was unexpectedly interrupted");
m_print("Terminated with: ");
m_print(m_client.getWriteError());
m_print(m_client->getWriteError());
setWriteError(SSL_CLIENT_WRTIE_ERROR);
stop();
}
return c_con && br_con && wr_ok;
}
/** see SSLClient.h */
template<class C>
int SSLClient<C>::m_run_until(const unsigned target) {
/** see SSLClientImpl.h*/
int SSLClientImpl::m_run_until(const unsigned target) {
for (;;) {
unsigned state = m_update_engine();
/*
@ -291,9 +279,8 @@ int SSLClient<C>::m_run_until(const unsigned target) {
}
}
/** see SSLClient.h */
template<class C>
unsigned SSLClient<C>::m_update_engine() {
/** see SSLClientImpl.h*/
unsigned SSLClientImpl::m_update_engine() {
for(;;) {
// get the state
unsigned state = br_ssl_engine_current_state(&m_sslctx.eng);
@ -308,7 +295,7 @@ unsigned SSLClient<C>::m_update_engine() {
int wlen;
buf = br_ssl_engine_sendrec_buf(&m_sslctx.eng, &len);
wlen = m_client.write(buf, len);
wlen = m_client->write(buf, len);
if (wlen < 0) {
m_print("Error writing to m_client");
/*
@ -381,9 +368,9 @@ unsigned SSLClient<C>::m_update_engine() {
size_t len;
unsigned char * buf = br_ssl_engine_recvrec_buf(&m_sslctx.eng, &len);
// do we have the record you're looking for?
if (m_client.available() >= len) {
if (m_client->available() >= len) {
// I suppose so!
int rlen = m_client.readBytes((char *)buf, len);
int rlen = m_client->readBytes((char *)buf, len);
if (rlen < 0) {
m_print("Error reading bytes from m_client");
setWriteError(SSL_BR_WRITE_ERROR);

View file

@ -18,8 +18,143 @@
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "bearssl.h"
#include "Client.h"
#include "Arduino.h"
#ifndef SSLClientImpl_H_
#define SSLClientImpl_H_
/** TODO: Write what this is */
class SSLClientImpl : public Client {
public:
/**
* @brief initializes SSL contexts for bearSSL
*
* @pre The client class must be able to access the internet, as SSLClient
* cannot manage this for you.
*
* @post set_client must be called immediatly after to set the client class
* pointer.
*
* @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
*/
explicit SSLClientImpl(Client* client, const br_x509_trust_anchor *trust_anchors, const size_t trust_anchors_num, const bool debug = true);
/** Dtor is implicit since unique_ptr handles it fine */
/** functions specific to the EthernetClient which I'll have to override */
// uint8_t status();
// uint8_t getSocketNumber() const;
/** functions dealing with read/write that BearSSL will be injected into */
/**
* @brief Connect over SSL to a host specified by an ip address
*
* SSLClient::connect(host, port) should be preffered over this function,
* as verifying the domain name is a step in ensuring the certificate is
* legitimate, which is important to the security of the device. Additionally,
* SSL sessions cannot be resumed, which can drastically increase initial
* connect time.
*
* This function initializes EthernetClient by calling EthernetClient::connect
* with the parameters supplied, then once the socket is open initializes
* the appropriete bearssl contexts using the TLS_only_profile. Due to the
* design of the SSL standard, this function will probably take an extended
* period (1-2sec) to negotiate the handshake and finish the connection.
*
* @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);
/**
* @brief Connect over SSL using connect(ip, port), but use a DNS lookup to
* get the IP Address first.
*
* This function initializes EthernetClient by calling EthernetClient::connect
* with the parameters supplied, then once the socket is open initializes
* the appropriete bearssl contexts using the TLS_only_profile.
*
* Due to the design of the SSL standard, this function will probably take an
* extended period (1-2sec) to negotiate the handshake and finish the
* connection. Since the hostname is provided, however, BearSSL is able to keep
* a session cache of the clients we have connected to. This should reduce
* connection time greatly. In order to use this feature, you must reuse the
* same SSLClient object to connect to the reused host. Doing this will allow
* BearSSL to automatically match the hostname to a cached session.
*
* @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);
virtual size_t write(uint8_t b) { return write(&b, 1); }
virtual size_t write(const uint8_t *buf, size_t size);
virtual int available();
virtual int read() { uint8_t read_val; return read(&read_val, 1) > 0 ? read_val : -1; }
virtual int read(uint8_t *buf, size_t size);
virtual int peek();
virtual void flush();
virtual void stop();
virtual uint8_t connected();
protected:
/**
* @brief set the pointer to the Client class that we wil use
*
* Call this function immediatly after the ctor. This functionality
* is placed in it's own function for flexibility reasons, but it
* is critical that this function is called before anything else
*/
void set_client(Client* c) { m_client = c; }
private:
/** @brief debugging print function, only prints if m_debug is true */
template<typename T>
constexpr void m_print(const T str) const {
if (m_debug) {
Serial.print("SSLClientImpl: ");
Serial.println(str);
}
}
/** run the bearssl engine until a certain state */
int m_run_until(const unsigned target);
/** proxy for availble that returns the state */
unsigned m_update_engine();
// hold a reference to the client
Client* m_client;
// store pointers to the trust anchors
// should not be computed at runtime
const br_x509_trust_anchor *m_trust_anchors;
const size_t m_trust_anchors_num;
// store whether to enable debug logging
const bool m_debug;
// 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, "m_iobuf must be below maximum buffer size");
// store the index of where we are writing in the buffer
// so we can send our records all at once to prevent
// weird timing issues
size_t m_write_idx;
// store the last error code
};
#endif /* SSLClientImpl_H_ */

View file

@ -23,6 +23,7 @@
*/
#include "bearssl.h"
#include "bearssl_ssl.h"
/*
* A "profile" is an initialisation function for a SSL context, that
@ -234,7 +235,7 @@ br_client_init_TLS12_only(br_ssl_client_context *cc,
// br_ssl_engine_set_default_ecdsa(&cc->eng);
//* Alternate: set implementations explicitly.
// br_ssl_client_set_rsapub(cc, &br_rsa_i31_public);
br_ssl_client_set_rsavrfy(cc, &br_rsa_i15_pkcs1_vrfy);
br_ssl_engine_set_rsavrfy(&cc->eng, &br_rsa_i15_pkcs1_vrfy);
br_ssl_engine_set_ec(&cc->eng, &br_ec_all_m15);
br_ssl_engine_set_ecdsa(&cc->eng, &br_ecdsa_i15_vrfy_asn1);
//*/