moved documentation and almost finished, need to fix forward definitions in header file

This commit is contained in:
Noah Laptop 2019-03-21 11:17:00 -07:00
parent 6389c24bc1
commit 6e7215b0b8
3 changed files with 306 additions and 181 deletions

View file

@ -112,10 +112,263 @@ public:
setTimeout(10 * 1000);
}
/*
* The special functions most clients have are below
* Most of them smply pass through
//========================================
//= Functions implemented in SSLClientImpl
//========================================
/**
* @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. Due to the design of the SSL standard,
* this function will probably take an extended period (1-4sec) to negotiate
* the handshake and finish the connection. This function runs until the SSL
* handshake succeeds or fails, as found in most Arduino libraries, so be
* sure to design around this in your code.
*
* The implementation for this function can be found in SSLClientImpl::connect(IPAddress, uint16_t)
*
* @pre The underlying client object (passed in through the ctor) in a non-
* error state, and must be able to access the server being connected to.
* @pre SSLCLient can only have one connection at a time, so the client
* object must not already have a socket open.
* @pre There must be sufficient memory availible on the device to verify
* the certificate (if the free memory drops below 8000 bytes during certain
* points in the connection, SSLCLient will fail).
* @pre There must be a trust anchor given to the ctor that corresponds to
* the certificate provided by the IP address being connected to. For more
* information check out the wiki on the pycert-bearssl tool.
* @pre The analog pin passed to the ctor must be set to input, and must
* be wired to something sort of random (floating is fine).
*
* @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)
*/
// virtual int connect(IPAddress ip, uint16_t port) = 0;
/**
* @brief Connect over SSL using connect(ip, port), using 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 uses BearSSL to
* to complete a SSL handshake. This function runs until the SSL handshake
* succeeds or fails, as found in most Arduino libraries.
*
* SSL requires the client to generate some random bits (to be later combined
* with some random bits from the server), so SSLClient uses the least signinigant
* bits from the analog pin supplied in the ctor. The random bits are generated
* from 16 consecutive analogReads, and given to BearSSL before the handshake
* starts.
*
* Due to the design of the SSL standard, this function will probably take an
* extended period (1-4sec) 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 to about 100-200ms. In order to use this feature, the website
* you are connecting to must support it (most do by default), you must
* reuse the same SSLClient object, and you must reconnect to the same server.
* SSLClient automatcally stores an IP address and hostname in each session,
* ensuring that if you call connect("www.google.com") SSLClient will use a
* cached IP address instead of another DNS lookup. Because some websites have
* multiple servers on a single IP address (github.com is an example), however,
* you may find that even if you are connecting to the same host the connection
* does not resume. This is a flaw in the SSL session protocol, and has been
* resolved in future versions. On top of all that, SSL sessions can expire
* based on server criteria, which will result in a regular connection time.
* Because of all these factors, it is generally prudent to assume the
* connection will not be resumed, and go from there.
*
* The implementation for this function can be found in SSLClientImpl::connect(const char*, uint16_t)
*
* @pre The underlying client object (passed in through the ctor) in a non-
* error state, and must be able to access the server being connected to.
* @pre SSLCLient can only have one connection at a time, so the client
* object must not already have a socket open.
* @pre There must be sufficient memory availible on the device to verify
* the certificate (if the free memory drops below 8000 bytes during certain
* points in the connection, SSLCLient will fail).
* @pre There must be a trust anchor given to the ctor that corresponds to
* the certificate provided by the IP address being connected to. For more
* information check out the wiki on the pycert-bearssl tool.
* @pre The analog pin passed to the ctor must be set to input, and must
* be wired to something sort of random (floating is fine).
*
* @param host The cstring host ("www.google.com")
* @param port the port to connect to (443)
* @returns 1 of success, 0 if failure (as found in EthernetClient).
*/
// virtual int connect(const char *host, uint16_t port) = 0;
/** @see SSLClient::write(uint8_t*, size_t) */
// virtual size_t write(uint8_t b) = 0;
/**
* @brief Write some bytes to the SSL connection
*
* Assuming all preconditions are met, this function waits for BearSSL
* to be ready for data to be sent, then writes data to the BearSSL IO
* buffer, BUT does not initally send the data. Insead, it is
* then checked if the BearSSL IO buffer is full, and if so, this function
* waits until BearSSL has flushed the buffer (written it to the
* network client) and fills the buffer again. If the function finds
* that the BearSSL buffer is not full, it returns the number of
* bytes written. In other words, this function will only write data
* to the network if the BearSSL IO buffer is full. Instead, you must call
* SSLClient::availible or SSLClient::flush, which will detect that
* the buffer is ready for writing, and will write the data to the network.
*
* This was implemented as a buffered function because users of Arduino Client
* libraries will often write to the network as such:
* @code{.cpp}
* Client client;
* ...
* client.println("GET /asciilogo.txt HTTP/1.1");
* client.println("Host: arduino.cc");
* client.println("Connection: close");
* while (!client.available()) { ... }
* ...
* @endcode
* This is fine with most network clients. With SSL, however, if we are encryting and
* writing to the network every write() call this will result in a lot of
* small encryption tasks. Encryption takes a lot of time and code, and in general
* the larger the batch we can do it in the better. For this reason, write()
* implicitly buffers until SSLClient::availible is called, or until the buffer is full.
* If you would like to trigger a network write manually without using the SSLClient::available,
* you can also call SSLClient::flush, which will write all data and return when finished.
*
* The implementation for this function can be found in SSLClientImpl::write(const uint8_t*, size_t)
*
* @pre The socket and SSL layer must be connected, meaning SSLClient::connected must be true.
* @pre BearSSL must not be waiting for the recipt of user data (if it is, there is
* probably an error with how the protocol in implemented in your code).
*
* @param buf the pointer to a buffer of bytes to copy
* @param size the number of bytes to copy from the buffer
* @returns The number of bytes copied to the buffer (size), or zero if the BearSSL engine
* fails to become ready for writing data.
*/
// virtual size_t write(const uint8_t *buf, size_t size) = 0;
/**
* @brief Returns the number of bytes availible to read from the SSL Socket
*
* This function updates the state of the SSL engine (including writing any data,
* see SSLClient::write) and as a result should be called periodically when writing
* or expecting data. Additionally, since this function returns zero if there are
* no bytes and if SSLClient::connected is false (this same behavior is found
* in EthernetClient), it is prudent to ensure in your own code that the
* preconditions are met before checking this function to prevent an ambigious
* result.
*
* The implementation for this function can be found in SSLClientImpl::available
*
* @pre SSLClient::connected must be true.
*
* @returns The number of bytes availible (can be zero), or zero if any of the pre
* conditions aren't satisfied.
*/
//virtual int available() = 0;
/** @see SSLClient::read(uint8_t*, size_t) */
//virtual int read() = 0;
/**
* @brief Read size bytes from the SSL socket buffer, copying them into *buf, and return the number of bytes read.
*
* This function checks if bytes are ready to be read by calling SSLClient::availible,
* and if so copies size number of bytes from the IO buffer into the buf pointer, and deletes
* that number of bytes from the SSLClient buffer. Data read using this function will not
* include any SSL or socket commands, as the Client and BearSSL will capture those and
* process them seperatley.
*
* It should be noted that a common problem I encountered with SSL connections is
* buffer overflow, caused by the server sending too much data at once. This problem
* is caused by the microcontroller being unable to copy and decrypt data faster
* than it is being recieved, forcing some data to be discarded. This usually puts BearSSL
* in an invalid state in which it is unable to recover, causing SSLClient to close
* the connection with a write error. If you are experiencing frequent timeout problems,
* this could be the reason why.
*
* In order to remedy this problem the device must be able to read the data faster than
* it is being recieved, or have a cache large enough to store the entire recieve payload.
* Since SSL's encryption forces the device to read slowly, this means we must increase
* the cache size. Depending on your platform, there are a number of ways this can be
* done:
* - Sometimes your communication sheild will have an internal buffer, which can be expanded
* through the driver code. This is the case with the Arduino Ethernet library (in the form
* of the MAX_SOCK_NUM and ETHERNET_LARGE_BUFFERS macros), however the library must be
* modified for the change to take effect.
* - SSLClient has an internal buffer SSLClientImpl::m_iobuf, which can be expanded. This will have very
* limited usefulness, however, as BearSSL limits the amount of data that can be processed
* based on the stage in the SSL handshake.
* - If none of the above are viable, it is possible to implement your own Client class which
* has an internal buffer much larger than both the driver and BearSSL. This would require
* in-depth knowlege of programming and the communication shield you are working with.
* Another important question to ask with this problem is: do I need to acsess this website?
* Often times there are other ways to get data that we need that do the same thing,
* and these other ways may offer smaller and more managable response payloads.
*
* The implementation for this function can be found in SSLClientImpl::read(uint8_t*, size_t)
*
* @pre SSLClient::available must be >0
*
* @param buf The pointer to the buffer to put SSL application data into
* @param size The size (in bytes) to copy to the buffer
*
* @returns The number of bytes copied (<= size), or -1 if the preconditions are not satisfied.
*/
//virtual int read(uint8_t *buf, size_t size) = 0;
/**
* @brief view the first byte of the buffer, without removing it from the SSLClient Buffer
* The implementation for this function can be found in SSLClientImpl::peek
* @pre SSLClient::available must be >0
* @returns The first byte recieved, or -1 if the preconditions are not satisfied (warning:
* do not use if your data may be -1, as the return value is ambigious)
*/
//virtual int peek() = 0;
/**
* @brief Force writing the buffered bytes from SSLClient::write to the network.
* This function is blocking until all bytes from the buffer are written. For
* an explanation of how writing with SSLClient works, please see SSLCLient::write.
* The implementation for this function can be found in SSLClientImpl::flush.
*/
//virtual void flush() = 0;
/**
* @brief Close the connection
* If the SSL session is still active, all incoming data is discarded and BearSSL will attempt to
* close the session gracefully (will write to the network), and then call m_client::stop. If the session is not active or an
* error was encountered previously, this function will simply call m_client::stop.
* The implementation for this function can be found in SSLClientImpl::peek.
*/
//virtual void stop() = 0;
/**
* @brief Check if the device is connected.
* Use this function to determine if SSLClient is still connected and a SSL connection is active.
* It should be noted that SSLClient::availible should be prefered over this function for rapid
* polling--both functions send and recieve data to the Client device, however SSLClient::availible
* has some delays built in to protect the Client device from being polled too frequently.
*
* The implementation for this function can be found in SSLClientImpl::connected.
*
* @returns 1 if connected, 0 if not
*/
//virtual uint8_t connected() = 0;
//========================================
//= Functions Not in the Client Interface
//========================================
/**
* @brief Equivalent to SSLClient::connected() > 0
* @returns true if connected, false if not

View file

@ -353,7 +353,17 @@ int SSLClientImpl::m_run_until(const unsigned target) {
if (state != lastState) {
lastState = state;
m_info("m_run changed state:", func_name);
printState(state);
if(m_debug == DebugLevel::SSL_INFO) {
m_info("State: ", __func__);
if(state == 0) Serial.println(" Invalid");
else if (state & BR_SSL_CLOSED) Serial.println(" Connection closed");
else {
if (state & BR_SSL_SENDREC) Serial.println(" SENDREC");
if (state & BR_SSL_RECVREC) Serial.println(" RECVREC");
if (state & BR_SSL_SENDAPP) Serial.println(" SENDAPP");
if (state & BR_SSL_RECVAPP) Serial.println(" RECVAPP");
}
}
}
if (state & BR_SSL_RECVREC) {
size_t len;

View file

@ -97,188 +97,51 @@ public:
explicit SSLClientImpl(Client* client, const br_x509_trust_anchor *trust_anchors,
const size_t trust_anchors_num, const int analog_pin, const DebugLevel debug);
/* 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. Due to the design of the SSL standard,
* this function will probably take an extended period (1-4sec) to negotiate
* the handshake and finish the connection. This function runs until the SSL
* handshake succeeds or fails, as found in most Arduino libraries, so be
* sure to design around this in your code.
*
* @pre The underlying client object (passed in through the ctor) in a non-
* error state, and must be able to access the server being connected to.
* @pre SSLCLient can only have one connection at a time, so the client
* object must not already have a socket open.
* @pre There must be sufficient memory availible on the device to verify
* the certificate (if the free memory drops below 8000 bytes during certain
* points in the connection, SSLCLient will fail).
* @pre There must be a trust anchor given to the ctor that corresponds to
* the certificate provided by the IP address being connected to. For more
* information check out the wiki on the pycert-bearssl tool.
* @pre The analog pin passed to the ctor must be set to input, and must
* be wired to something sort of random (floating is fine).
*
* @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)
*/
//============================================
//= Functions implemented in SSLClientImpl.cpp
//============================================
/** @see SSLClient::connect(IPAddress, uint16_t) */
virtual int connect(IPAddress ip, uint16_t port);
/**
* @brief Connect over SSL using connect(ip, port), using 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 uses BearSSL to
* to complete a SSL handshake. This function runs until the SSL handshake
* succeeds or fails, as found in most Arduino libraries.
*
* SSL requires the client to generate some random bits (to be later combined
* with some random bits from the server), so SSLClient uses the least signinigant
* bits from the analog pin supplied in the ctor. The random bits are generated
* from 16 consecutive analogReads, and given to BearSSL before the handshake
* starts.
*
* Due to the design of the SSL standard, this function will probably take an
* extended period (1-4sec) 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 to about 100-200ms. In order to use this feature, the website
* you are connecting to must support it (most do by default), you must
* reuse the same SSLClient object, and you must reconnect to the same server.
* SSLClient automatcally stores an IP address and hostname in each session,
* ensuring that if you call connect("www.google.com") SSLClient will use a
* cached IP address instead of another DNS lookup. Because some websites have
* multiple servers on a single IP address (github.com is an example), however,
* you may find that even if you are connecting to the same host the connection
* does not resume. This is a flaw in the SSL session protocol, and has been
* resolved in future versions. On top of all that, SSL sessions can expire
* based on server criteria, which will result in a regular connection time.
* Because of all these factors, it is generally prudent to assume the
* connection will not be resumed, and go from there.
*
* @pre The underlying client object (passed in through the ctor) in a non-
* error state, and must be able to access the server being connected to.
* @pre SSLCLient can only have one connection at a time, so the client
* object must not already have a socket open.
* @pre There must be sufficient memory availible on the device to verify
* the certificate (if the free memory drops below 8000 bytes during certain
* points in the connection, SSLCLient will fail).
* @pre There must be a trust anchor given to the ctor that corresponds to
* the certificate provided by the IP address being connected to. For more
* information check out the wiki on the pycert-bearssl tool.
* @pre The analog pin passed to the ctor must be set to input, and must
* be wired to something sort of random (floating is fine).
*
* @param host The cstring host ("www.google.com")
* @param port the port to connect to (443)
* @returns 1 of success, 0 if failure (as found in EthernetClient).
*/
/** @see SSLClient::connect(const char*, uint16_t) */
virtual int connect(const char *host, uint16_t port);
/** @see SSLClient::write(uint8_t*, size_t) */
/** @see SSLClient::write(const uint8_t*, size_t) */
virtual size_t write(uint8_t b) { return write(&b, 1); }
/**
* @brief Write some bytes to the SSL connection
*
* Assuming all preconditions are met, this function waits for BearSSL
* to be ready for data to be sent, then writes data to the BearSSL IO
* buffer, BUT does not initally send the data. Insead, it is
* then checked if the BearSSL IO buffer is full, and if so, this function
* waits until BearSSL has flushed the buffer (written it to the
* network client) and fills the buffer again. If the function finds
* that the BearSSL buffer is not full, it returns the number of
* bytes written. In other words, this function will only write data
* to the network if the BearSSL IO buffer is full. Instead, you must call
* SSLClient::availible or SSLClient::flush, which will detect that
* the buffer is ready for writing, and will write the data to the network.
*
* This was implemented as a buffered function because users of Arduino Client
* libraries will often write to the network as such:
* @code{.cpp}
* Client client;
* ...
* client.println("GET /asciilogo.txt HTTP/1.1");
* client.println("Host: arduino.cc");
* client.println("Connection: close");
* while (!client.available()) { ... }
* ...
* @endcode
* This is fine with most network clients. With SSL, however, if we are encryting and
* writing to the network every write() call this will result in a lot of
* small encryption tasks. Encryption takes a lot of time and code, and in general
* the larger the batch we can do it in the better. For this reason, write()
* implicitly buffers until SSLClient::availible is called, or until the buffer is full.
* If you would like to trigger a network write manually without using the SSLClient::available,
* you can also call SSLClient::flush, which will write all data and return when finished.
*
* @pre The socket and SSL layer must be connected, meaning SSLClient::connected must be true.
* @pre BearSSL must not be waiting for the recipt of user data (if it is, there is
* probably an error with how the protocol in implemented in your code).
*
* @param buf the pointer to a buffer of bytes to copy
* @param size the number of bytes to copy from the buffer
* @returns The number of bytes copied to the buffer (size), or zero if the BearSSL engine
* fails to become ready for writing data.
*/
/** @see SSLClient::write(const uint8_t*, size_t) */
virtual size_t write(const uint8_t *buf, size_t size);
/**
* @brief Returns the number of bytes availible to read from the SSL Socket
*
* This function updates the state of the SSL engine (including writing any data,
* see SSLClient::write) and as a result should be called periodically when writing
* or expecting data. Additionally, since this function returns zero if there are
* no bytes and if SSLClient::connected is false (this same behavior is found
* in EthernetClient), it is prudent to ensure in your own code that the
* preconditions are met before checking this function to prevent an ambigious
* result.
*
* @pre SSLClient::connected must be true.
*
* @returns The number of bytes availible (can be zero), or zero if any of the pre
* conditions aren't satisfied.
*/
/** @see SSLClient::available */
virtual int available();
/** @see SSLClient::read(uint8_t*, size_t) */
virtual int read() { uint8_t read_val; return read(&read_val, 1) > 0 ? read_val : -1; }
/**
* @brief Read size bytes from the SSL socket buffer, copying them into *buf, and return the number of bytes read.
*
* This function checks if bytes are ready to be read by calling SSLClient::availible,
* and if there are some copies size number of bytes from the IO buffer into buf.
*
* It should be noted that a common problem I encountered with SSL connections is
* buffer overflow, caused by the server sending too much data at once. This problem
* only occurs...
*
* TODO: finish
*/
/** @see SSLClient::read(uint8_t*, size_t) */
virtual int read(uint8_t *buf, size_t size);
/** @see SSLClient::peek */
virtual int peek();
/** @see SSLClient::flush */
virtual void flush();
/** @see SSLClient::stop */
virtual void stop();
/** @see SSLClient::connected */
virtual uint8_t connected();
// stub virtual functions to get things from the client
//============================================
//= Functions implemented in SSLClient.h
//============================================
/** See SSLClient::localPort */
virtual uint16_t localPort() = 0;
/** See SSLClient::remoteIP */
virtual IPAddress remoteIP() = 0;
/** See SSLClient::localPort */
virtual uint16_t remotePort() = 0;
// as well as store and retrieve session data
/** See SSLClient::getSession */
virtual SSLSession& getSession(const char* host, const IPAddress& addr) = 0;
protected:
//============================================
//= Functions implemented in SSLClientImpl.cpp
//============================================
/**
* @brief set the pointer to the Client class that we wil use
*
@ -319,19 +182,6 @@ protected:
void m_error(const T str, const char* func_name) const { m_print(str, func_name, SSL_ERROR); }
private:
void printState(unsigned state) const {
if(m_debug == DebugLevel::SSL_INFO) {
m_info("State: ", __func__);
if(state == 0) Serial.println(" Invalid");
else if (state & BR_SSL_CLOSED) Serial.println(" Connection closed");
else {
if (state & BR_SSL_SENDREC) Serial.println(" SENDREC");
if (state & BR_SSL_RECVREC) Serial.println(" RECVREC");
if (state & BR_SSL_SENDAPP) Serial.println(" SENDAPP");
if (state & BR_SSL_RECVAPP) Serial.println(" RECVAPP");
}
}
}
/** Returns whether or not the engine is connected, without polling the client over SPI or other (as opposed to connected()) */
bool m_soft_connected(const char* func_name);
/** start the ssl engine on the connected client */
@ -340,6 +190,11 @@ private:
int m_run_until(const unsigned target);
/** proxy for availble that returns the state */
unsigned m_update_engine();
//============================================
//= Data Members
//============================================
// hold a reference to the client
Client* m_client;
// store pointers to the trust anchors
@ -359,6 +214,13 @@ private:
// simply edit this value to change the buffer size to the desired value
// additionally, we need to correct buffer size based off of how many sessions we decide to cache
// since SSL takes so much memory if we don't it will cause the stack and heap to collide
/**
* @brief The internal buffer to use with BearSSL.
* This buffer controls how much data BearSSL can encrypt/decrypt at a given time. It can be expanded
* or shrunk to [255, BR_SSL_BUFSIZE_BIDI], depending on the memory and speed needs of your application.
* As a rule of thumb SSLClient will fail if it does not have at least 8000 bytes when starting a
* connection.
*/
unsigned char m_iobuf[BR_SSL_BUFSIZE_MONO / 4];
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