refactored a bit, in process of documenting
This commit is contained in:
parent
292dd7a807
commit
6389c24bc1
4 changed files with 334 additions and 106 deletions
|
@ -24,11 +24,8 @@
|
||||||
* This library was created to provide SSL functionality to the {@link https://learn.adafruit.com/adafruit-wiz5500-wiznet-ethernet-featherwing/overview}
|
* This library was created to provide SSL functionality to the {@link https://learn.adafruit.com/adafruit-wiz5500-wiznet-ethernet-featherwing/overview}
|
||||||
* Adafruit Ethernet shield. Since this shield does not implement SSL functionality on
|
* Adafruit Ethernet shield. Since this shield does not implement SSL functionality on
|
||||||
* its own, we need to use an external library: in this case BearSSL {@link https://bearssl.org/},
|
* its own, we need to use an external library: in this case BearSSL {@link https://bearssl.org/},
|
||||||
* which is also use in the Arduino ESP8266 core. SSLClient will serve to implement the
|
* which is also used in the Arduino ESP8266 core. SSLClient will serve to implement the
|
||||||
* BearSSL functionality inbetween EthernetCLient and the User, such that the user will
|
* BearSSL functionality inbetween EthernetClient and the User, such that the user.
|
||||||
* simply need to start with:
|
|
||||||
* SSLCLient client(ethCLient);
|
|
||||||
* And then call the functions they normally would with EthernetClient using SSLCLient.
|
|
||||||
*
|
*
|
||||||
* This file specifically controls the class templating used to allow SSLClient to interface
|
* This file specifically controls the class templating used to allow SSLClient to interface
|
||||||
* with all of the CLient-based classes. To see details on the implementations of the functions
|
* with all of the CLient-based classes. To see details on the implementations of the functions
|
||||||
|
@ -38,12 +35,17 @@
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include "Client.h"
|
#include "Client.h"
|
||||||
#include "SSLClientImpl.h"
|
#include "SSLClientImpl.h"
|
||||||
|
#include "SSLSession.h"
|
||||||
|
|
||||||
#ifndef SSLClient_H_
|
#ifndef SSLClient_H_
|
||||||
#define SSLClient_H_
|
#define SSLClient_H_
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief This class serves as a templating proxy class for the SSLClientImpl to do the real work.
|
* \brief The main SSLClient class
|
||||||
|
*
|
||||||
|
* TODO: fix this blurb
|
||||||
|
*
|
||||||
|
* 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
|
* 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
|
* this class in a single line of code (e.g. SSLClient(EthernetClient())), but I also
|
||||||
|
@ -59,16 +61,23 @@
|
||||||
|
|
||||||
template <class C, size_t SessionCache = 1>
|
template <class C, size_t SessionCache = 1>
|
||||||
class SSLClient : public SSLClientImpl {
|
class SSLClient : public SSLClientImpl {
|
||||||
/** static type checks
|
/**
|
||||||
|
* static checks
|
||||||
* I'm a java developer, so I want to ensure that my inheritance is safe.
|
* 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
|
* 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
|
* actually present on class C. It does this by checking that the
|
||||||
* class inherits from Client, and then that it contains a status() function.
|
* class inherits from Client.
|
||||||
|
*
|
||||||
|
* Additionally, I ran into a lot of memory issues with large sessions caches.
|
||||||
|
* Since each session contains at max 352 bytes of memory, they eat of the
|
||||||
|
* stack quite quickly and can cause overflows. As a result, I have added a
|
||||||
|
* warning here to discourage the use of more than 3 sessions at a time. Any
|
||||||
|
* amount past that will require special modification of this library, and
|
||||||
|
* assumes you know what you are doing.
|
||||||
*/
|
*/
|
||||||
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(SessionCache > 0 && SessionCache < 255, "There can be no less than one and no more than 255 sessions in the cache!");
|
static_assert(SessionCache > 0 && SessionCache < 255, "There can be no less than one and no more than 255 sessions in the cache!");
|
||||||
static_assert(SessionCache <= 3, "You need to decrease the size of m_iobuf in order to have more than 3 sessions at once, otherwise memory issues will occur.");
|
static_assert(SessionCache <= 3, "You need to decrease the size of m_iobuf in order to have more than 3 sessions at once, otherwise memory issues will occur.");
|
||||||
// static_assert(std::is_function<decltype(C::status)>::value, "C must have a status() function!");
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
|
@ -77,9 +86,10 @@ public:
|
||||||
* 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.
|
||||||
*
|
*
|
||||||
* @pre The client class must be able to access the internet, as SSLClient
|
* @pre You will need to generate an array of trust_anchors (root certificates)
|
||||||
* cannot manage this for you. Additionally it is recommended that the analog_pin
|
* based off of the domains you want to make SSL connections to. Check out the
|
||||||
* be set to input.
|
* Wiki on the pycert-bearssl tool for a simple way to do this.
|
||||||
|
* @pre The analog_pin should be set to input.
|
||||||
*
|
*
|
||||||
* @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
|
||||||
|
@ -88,13 +98,12 @@ public:
|
||||||
* @param analog_pin An analog pin to pull random bytes from, used in seeding the RNG
|
* @param analog_pin An analog pin to pull random bytes from, used in seeding the RNG
|
||||||
* @param debug whether to enable or disable debug logging, must be constexpr
|
* @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 int analog_pin, const DebugLevel debug = SSL_ERROR)
|
explicit SSLClient(const C& client, const br_x509_trust_anchor *trust_anchors, const size_t trust_anchors_num, const int analog_pin, const DebugLevel debug = SSL_WARN)
|
||||||
: SSLClientImpl(NULL, trust_anchors, trust_anchors_num, analog_pin, debug)
|
: SSLClientImpl(NULL, trust_anchors, trust_anchors_num, analog_pin, debug)
|
||||||
, m_client(client)
|
, m_client(client)
|
||||||
, m_sessions{SSLSession()}
|
, m_sessions{SSLSession()}
|
||||||
, m_index(0)
|
, m_index(0)
|
||||||
{
|
{
|
||||||
// for (uint8_t i = 0; i < SessionCache; i++) m_sessions[i] = SSLSession();
|
|
||||||
// since we are copying the client in the ctor, we have to set
|
// since we are copying the client in the ctor, we have to set
|
||||||
// the client pointer after the class is constructed
|
// the client pointer after the class is constructed
|
||||||
set_client(&m_client);
|
set_client(&m_client);
|
||||||
|
@ -106,23 +115,69 @@ public:
|
||||||
/*
|
/*
|
||||||
* The special functions most clients have are below
|
* The special functions most clients have are below
|
||||||
* Most of them smply pass through
|
* Most of them smply pass through
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @brief Equivalent to SSLClient::connected() > 0
|
||||||
|
* @returns true if connected, false if not
|
||||||
*/
|
*/
|
||||||
virtual operator bool() { return connected() > 0; }
|
virtual operator bool() { return connected() > 0; }
|
||||||
|
/** {@link SSLClient::bool()} */
|
||||||
virtual bool operator==(const bool value) { return bool() == value; }
|
virtual bool operator==(const bool value) { return bool() == value; }
|
||||||
|
/** {@link SSLClient::bool()} */
|
||||||
virtual bool operator!=(const bool value) { return bool() != value; }
|
virtual bool operator!=(const bool value) { return bool() != value; }
|
||||||
|
/** @brief Returns whether or not two SSLClient objects have the same underlying client object */
|
||||||
virtual bool operator==(const C& rhs) { return m_client == rhs; }
|
virtual bool operator==(const C& rhs) { return m_client == rhs; }
|
||||||
|
/** @brief Returns whether or not two SSLClient objects do not have the same underlying client object */
|
||||||
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 std::is_member_function_pointer<decltype(&C::localPort)>::value ? m_client.localPort() : 0; }
|
/** @brief Returns the local port, if the Client class has a localPort() function. Else return 0. */
|
||||||
virtual IPAddress remoteIP() { return std::is_member_function_pointer<decltype(&C::remoteIP)>::value ? m_client.remoteIP() : INADDR_NONE; }
|
virtual uint16_t localPort() {
|
||||||
virtual uint16_t remotePort() { return std::is_member_function_pointer<decltype(&C::remotePort)>::value ? m_client.remotePort() : 0; }
|
if (std::is_member_function_pointer<decltype(&C::localPort)>::value) return m_client.localPort();
|
||||||
|
else {
|
||||||
|
m_warn("Client class has no localPort function, so localPort() will always return 0", __func__);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** @brief Returns the remote IP, if the Client class has a remoteIP() function. Else return INADDR_NONE. */
|
||||||
|
virtual IPAddress remoteIP() {
|
||||||
|
if (std::is_member_function_pointer<decltype(&C::remoteIP)>::value) return m_client.remoteIP();
|
||||||
|
else {
|
||||||
|
m_warn("Client class has no remoteIP function, so remoteIP() will always return INADDR_NONE. This means that sessions caching will always be disabled.", __func__);
|
||||||
|
return INADDR_NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** @brief Returns the remote port, if the Client class has a remotePort() function. Else return 0. */
|
||||||
|
virtual uint16_t remotePort() {
|
||||||
|
if (std::is_member_function_pointer<decltype(&C::remotePort)>::value) return m_client.remotePort();
|
||||||
|
else {
|
||||||
|
m_warn("Client class has no remotePort function, so remotePort() will always return 0", __func__);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//! get the client object
|
/** @brief returns a refernence to the client object stored in this class. Take care not to break it. */
|
||||||
C& getClient() { return m_client; }
|
C& getClient() { return m_client; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get a sesssion reference corressponding to a host and IP, or a reference to a emptey session if none exist
|
||||||
|
*
|
||||||
|
* If no session corresponding to the host and ip exist, then this function will cycle through
|
||||||
|
* sessions in a rotating order. This allows the ssession cache to continuially store sessions,
|
||||||
|
* however it will also result in old sessions being cleared and returned. In general, it is a
|
||||||
|
* good idea to use a SessionCache size equal to the number of domains you plan on connecting to.
|
||||||
|
*
|
||||||
|
* @param host A hostname c string, or NULL if one is not availible
|
||||||
|
* @param ip An IP address
|
||||||
|
* @returns A reference to an SSLSession object
|
||||||
|
*/
|
||||||
virtual SSLSession& getSession(const char* host, const IPAddress& addr);
|
virtual SSLSession& getSession(const char* host, const IPAddress& addr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clear the session corresponding to a host and IP
|
||||||
|
*
|
||||||
|
* @param host A hostname c string, or NULL if one is not availible
|
||||||
|
* @param ip An IP address
|
||||||
|
*/
|
||||||
virtual void removeSession(const char* host, const IPAddress& addr);
|
virtual void removeSession(const char* host, const IPAddress& addr);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// utility function to find a session index based off of a host and IP
|
// utility function to find a session index based off of a host and IP
|
||||||
int m_getSessionIndex(const char* host, const IPAddress& addr) const;
|
int m_getSessionIndex(const char* host, const IPAddress& addr) const;
|
||||||
|
|
|
@ -20,6 +20,35 @@
|
||||||
|
|
||||||
#include "SSLClient.h"
|
#include "SSLClient.h"
|
||||||
|
|
||||||
|
// system reset definitions
|
||||||
|
static constexpr auto SYSRESETREQ = (1<<2);
|
||||||
|
static constexpr auto VECTKEY = (0x05fa0000UL);
|
||||||
|
static constexpr auto VECTKEY_MASK = (0x0000ffffUL);
|
||||||
|
/** Trigger a software reset. Only use if in unrecoverable state */
|
||||||
|
[[ noreturn ]] static void RESET() {
|
||||||
|
(*(uint32_t*)0xe000ed0cUL)=((*(uint32_t*)0xe000ed0cUL)&VECTKEY_MASK)|VECTKEY|SYSRESETREQ;
|
||||||
|
while(1) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __arm__
|
||||||
|
// should use uinstd.h to define sbrk but Due causes a conflict
|
||||||
|
extern "C" char* sbrk(int incr);
|
||||||
|
#else // __ARM__
|
||||||
|
extern char *__brkval;
|
||||||
|
#endif // __arm__
|
||||||
|
|
||||||
|
/** Get the free memory availible in bytes (stack - heap) */
|
||||||
|
static int freeMemory() {
|
||||||
|
char top;
|
||||||
|
#ifdef __arm__
|
||||||
|
return &top - reinterpret_cast<char*>(sbrk(0));
|
||||||
|
#elif defined(CORE_TEENSY) || (ARDUINO > 103 && ARDUINO != 151)
|
||||||
|
return &top - __brkval;
|
||||||
|
#else // __arm__
|
||||||
|
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
|
||||||
|
#endif // __arm__
|
||||||
|
}
|
||||||
|
|
||||||
/** see SSLClientImpl.h */
|
/** see SSLClientImpl.h */
|
||||||
SSLClientImpl::SSLClientImpl(Client *client, const br_x509_trust_anchor *trust_anchors,
|
SSLClientImpl::SSLClientImpl(Client *client, const br_x509_trust_anchor *trust_anchors,
|
||||||
const size_t trust_anchors_num, const int analog_pin, const DebugLevel debug)
|
const size_t trust_anchors_num, const int analog_pin, const DebugLevel debug)
|
||||||
|
@ -473,6 +502,16 @@ unsigned SSLClientImpl::m_update_engine() {
|
||||||
const auto avail = m_client->available();
|
const auto avail = m_client->available();
|
||||||
if (avail >= len) {
|
if (avail >= len) {
|
||||||
int mem = freeMemory();
|
int mem = freeMemory();
|
||||||
|
// check for a stack overflow
|
||||||
|
// if the stack overflows we basically have to crash, and
|
||||||
|
// hope the user is ok with that
|
||||||
|
// since all memory is garbage we can't trust the cpu to
|
||||||
|
// execute anything properly
|
||||||
|
if (mem > 32 * 1024) {
|
||||||
|
// software reset
|
||||||
|
RESET();
|
||||||
|
}
|
||||||
|
// debug info
|
||||||
m_info("Memory: ", func_name);
|
m_info("Memory: ", func_name);
|
||||||
m_info(mem, func_name);
|
m_info(mem, func_name);
|
||||||
// free memory check
|
// free memory check
|
||||||
|
@ -485,17 +524,6 @@ unsigned SSLClientImpl::m_update_engine() {
|
||||||
stop();
|
stop();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// check for a stack overflow
|
|
||||||
// if the stack overflows we basically have to crash, and
|
|
||||||
// hope the user is ok with that
|
|
||||||
// since all memory is garbage we can't trust the cpu to
|
|
||||||
// execute anything properly
|
|
||||||
if (mem > 32 * 1024) {
|
|
||||||
// software reset
|
|
||||||
REQUEST_EXTERNAL_RESET;
|
|
||||||
// can't print anything, so pray for reset
|
|
||||||
while (1) { }
|
|
||||||
}
|
|
||||||
m_info("Read bytes from client: ", func_name);
|
m_info("Read bytes from client: ", func_name);
|
||||||
m_info(avail, func_name);
|
m_info(avail, func_name);
|
||||||
m_info(len, func_name);
|
m_info(len, func_name);
|
||||||
|
|
|
@ -26,68 +26,60 @@
|
||||||
#ifndef SSLClientImpl_H_
|
#ifndef SSLClientImpl_H_
|
||||||
#define SSLClientImpl_H_
|
#define SSLClientImpl_H_
|
||||||
|
|
||||||
// system reset definitions
|
/**
|
||||||
#define SYSRESETREQ (1<<2)
|
* @brief Static constants defining the possible errors encountered.
|
||||||
#define VECTKEY (0x05fa0000UL)
|
*
|
||||||
#define VECTKEY_MASK (0x0000ffffUL)
|
* If SSLClient encounters an error, it will generally output
|
||||||
#define AIRCR (*(uint32_t*)0xe000ed0cUL) // fixed arch-defined address
|
* logs into the serial moniter. If you need a way of programmatically
|
||||||
#define REQUEST_EXTERNAL_RESET (AIRCR=(AIRCR&VECTKEY_MASK)|VECTKEY|SYSRESETREQ)
|
* checking the errors, you can do so with SSLCLient.getWriteError(),
|
||||||
|
* which will return one of these values.
|
||||||
/** error enums
|
|
||||||
* Static constants defining the possible errors encountered
|
|
||||||
* Read from getWriteError();
|
|
||||||
*/
|
*/
|
||||||
enum Error {
|
enum Error {
|
||||||
SSL_OK = 0,
|
SSL_OK = 0,
|
||||||
|
/** The underlying client failed to connect, probably not an issue with SSL */
|
||||||
SSL_CLIENT_CONNECT_FAIL,
|
SSL_CLIENT_CONNECT_FAIL,
|
||||||
|
/** BearSSL failed to complete the SSL handshake, check logs for bear ssl error output */
|
||||||
SSL_BR_CONNECT_FAIL,
|
SSL_BR_CONNECT_FAIL,
|
||||||
|
/** The underlying client failed to write a payload, probably not an issue with SSL */
|
||||||
SSL_CLIENT_WRTIE_ERROR,
|
SSL_CLIENT_WRTIE_ERROR,
|
||||||
|
/** An internal error occurred with BearSSL, check logs for diagnosis. */
|
||||||
SSL_BR_WRITE_ERROR,
|
SSL_BR_WRITE_ERROR,
|
||||||
|
/** An internal error occured with SSLClient, and you probably need to submit an issue on Github. */
|
||||||
SSL_INTERNAL_ERROR,
|
SSL_INTERNAL_ERROR,
|
||||||
|
/** SSLClient detected that there was not enough memory (>8000 bytes) to continue. */
|
||||||
SSL_OUT_OF_MEMORY
|
SSL_OUT_OF_MEMORY
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Debug level enum
|
/**
|
||||||
* Static enum defining the debugging levels to print
|
* @brief Level of verbosity used in logging for SSLClient.
|
||||||
* into the Serial monitor
|
*
|
||||||
|
* Use these values when initializing SSLCLient to set how many logs you
|
||||||
|
* would like to see in the Serial moniter.
|
||||||
*/
|
*/
|
||||||
enum DebugLevel {
|
enum DebugLevel {
|
||||||
|
/** No logging output */
|
||||||
SSL_NONE = 0,
|
SSL_NONE = 0,
|
||||||
|
/** Only output errors that result in connection failure */
|
||||||
SSL_ERROR = 1,
|
SSL_ERROR = 1,
|
||||||
|
/** Ouput errors and warnings (useful when just starting to develop) */
|
||||||
SSL_WARN = 2,
|
SSL_WARN = 2,
|
||||||
|
/** Output errors, warnings, and internal information (very verbose) */
|
||||||
SSL_INFO = 3,
|
SSL_INFO = 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
#ifdef __arm__
|
* On error, any function in this class will terminate the socket.
|
||||||
// should use uinstd.h to define sbrk but Due causes a conflict
|
* TODO: Write what this is */
|
||||||
extern "C" char* sbrk(int incr);
|
|
||||||
#else // __ARM__
|
|
||||||
extern char *__brkval;
|
|
||||||
#endif // __arm__
|
|
||||||
|
|
||||||
static int freeMemory() {
|
|
||||||
char top;
|
|
||||||
#ifdef __arm__
|
|
||||||
return &top - reinterpret_cast<char*>(sbrk(0));
|
|
||||||
#elif defined(CORE_TEENSY) || (ARDUINO > 103 && ARDUINO != 151)
|
|
||||||
return &top - __brkval;
|
|
||||||
#else // __arm__
|
|
||||||
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
|
|
||||||
#endif // __arm__
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** TODO: Write what this is */
|
|
||||||
|
|
||||||
class SSLClientImpl : public Client {
|
class SSLClientImpl : public Client {
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief initializes SSL contexts for bearSSL
|
* @brief initializes SSL contexts for bearSSL
|
||||||
*
|
*
|
||||||
* @pre The client class must be able to access the internet, as SSLClient
|
* @pre You will need to generate an array of trust_anchors (root certificates)
|
||||||
* cannot manage this for you. Additionally it is recommended that the analog_pin
|
* based off of the domains you want to make SSL connections to. Check out the
|
||||||
* be set to input.
|
* Wiki on the pycert-bearssl tool for a simple way to do this.
|
||||||
|
* @pre The analog_pin should be set to input.
|
||||||
*
|
*
|
||||||
* @post set_client must be called immediatly after to set the client class
|
* @post set_client must be called immediatly after to set the client class
|
||||||
* pointer.
|
* pointer.
|
||||||
|
@ -104,13 +96,8 @@ public:
|
||||||
*/
|
*/
|
||||||
explicit SSLClientImpl(Client* client, const br_x509_trust_anchor *trust_anchors,
|
explicit SSLClientImpl(Client* client, const br_x509_trust_anchor *trust_anchors,
|
||||||
const size_t trust_anchors_num, const int analog_pin, const DebugLevel debug);
|
const size_t trust_anchors_num, const int analog_pin, const DebugLevel debug);
|
||||||
/** Dtor is implicit since unique_ptr handles it fine */
|
|
||||||
|
|
||||||
/** functions specific to the EthernetClient which I'll have to override */
|
/* functions dealing with read/write that BearSSL will be injected into */
|
||||||
// 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
|
* @brief Connect over SSL to a host specified by an ip address
|
||||||
*
|
*
|
||||||
|
@ -122,46 +109,162 @@ public:
|
||||||
*
|
*
|
||||||
* This function initializes EthernetClient by calling EthernetClient::connect
|
* This function initializes EthernetClient by calling EthernetClient::connect
|
||||||
* with the parameters supplied, then once the socket is open initializes
|
* with the parameters supplied, then once the socket is open initializes
|
||||||
* the appropriete bearssl contexts using the TLS_only_profile. Due to the
|
* the appropriete bearssl contexts. Due to the design of the SSL standard,
|
||||||
* design of the SSL standard, this function will probably take an extended
|
* this function will probably take an extended period (1-4sec) to negotiate
|
||||||
* period (1-2sec) to negotiate the handshake and finish the connection.
|
* 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 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);
|
virtual int connect(IPAddress ip, uint16_t port);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Connect over SSL using connect(ip, port), but use a DNS lookup to
|
* @brief Connect over SSL using connect(ip, port), using a DNS lookup to
|
||||||
* get the IP Address first.
|
* get the IP Address first.
|
||||||
*
|
*
|
||||||
* This function initializes EthernetClient by calling EthernetClient::connect
|
* This function initializes EthernetClient by calling EthernetClient::connect
|
||||||
* with the parameters supplied, then once the socket is open initializes
|
* with the parameters supplied, then once the socket is open uses BearSSL to
|
||||||
* the appropriete bearssl contexts using the TLS_only_profile.
|
* 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
|
* 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
|
* extended period (1-4sec) to negotiate the handshake and finish the
|
||||||
* connection. Since the hostname is provided, however, BearSSL is able to keep
|
* 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
|
* 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
|
* connection time to about 100-200ms. In order to use this feature, the website
|
||||||
* same SSLClient object to connect to the reused host. Doing this will allow
|
* you are connecting to must support it (most do by default), you must
|
||||||
* BearSSL to automatically match the hostname to a cached session.
|
* 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 host The cstring host ("www.google.com")
|
||||||
* @param port the port to connect to
|
* @param port the port to connect to (443)
|
||||||
* @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);
|
virtual int connect(const char *host, uint16_t port);
|
||||||
|
|
||||||
|
/** @see SSLClient::write(uint8_t*, size_t) */
|
||||||
virtual size_t write(uint8_t b) { return write(&b, 1); }
|
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.
|
||||||
|
*/
|
||||||
virtual size_t write(const uint8_t *buf, size_t size);
|
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.
|
||||||
|
*/
|
||||||
virtual int 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; }
|
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
|
||||||
|
*/
|
||||||
virtual int read(uint8_t *buf, size_t size);
|
virtual int read(uint8_t *buf, size_t size);
|
||||||
virtual int peek();
|
virtual int peek();
|
||||||
virtual void flush();
|
virtual void flush();
|
||||||
|
|
|
@ -50,13 +50,59 @@
|
||||||
class SSLSession : public br_ssl_session_parameters {
|
class SSLSession : public br_ssl_session_parameters {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief SSLSession constructor
|
||||||
|
*
|
||||||
|
* Sets all parameters to zero, and invalidates the session
|
||||||
|
*/
|
||||||
explicit SSLSession()
|
explicit SSLSession()
|
||||||
: m_valid_session(false)
|
: m_valid_session(false)
|
||||||
, m_hostname()
|
, m_hostname()
|
||||||
, m_ip(INADDR_NONE) {}
|
, m_ip(INADDR_NONE) {}
|
||||||
|
|
||||||
|
/** @brief use clear_parameters or set_parameters instead */
|
||||||
|
SSLSession& operator=(const SSLSession&) = delete;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \pre must call br_ssl_engine_get_session_parameters(engine, toBearSSlSession());
|
* @brief Get the hostname string associated with this session
|
||||||
|
*
|
||||||
|
* @returns A String object or "" if there is no hostname
|
||||||
|
* @pre must check isValidSession before getting this value,
|
||||||
|
* as if this session in invalid this value is not guarented
|
||||||
|
* to be reset to "".
|
||||||
|
*/
|
||||||
|
const String& get_hostname() const { return m_hostname; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get ::IPAddress associated with this session
|
||||||
|
*
|
||||||
|
* @returns A ::IPAddress object, #INADDR_NONE if there is no IP
|
||||||
|
* @pre must check isValidSession before getting this value,
|
||||||
|
* as if this session in invalid this value is not guarented
|
||||||
|
* to be reset to #INADDR_NONE.
|
||||||
|
*/
|
||||||
|
const IPAddress& get_ip() const { return m_ip; }
|
||||||
|
|
||||||
|
bool is_valid_session() const { return m_valid_session; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the ip address and hostname of the session.
|
||||||
|
*
|
||||||
|
* This function stores the ip Address object and hostname object into
|
||||||
|
* the session object. If hostname is not null or ip address is
|
||||||
|
* not blank, and the ::br_ssl_session_parameters values are non-zero
|
||||||
|
* it then validates the session.
|
||||||
|
*
|
||||||
|
* @pre You must call
|
||||||
|
* ::br_ssl_engine_get_session_parameters
|
||||||
|
* with this session before calling this function. This is because
|
||||||
|
* there is no way to completly validate the ::br_ssl_session_parameters
|
||||||
|
* and the session may end up in a corrupted state if this is not observed.
|
||||||
|
*
|
||||||
|
* @param ip The IP address of the host associated with the session
|
||||||
|
* @param hostname The string hostname ("www.google.com") associated with the session.
|
||||||
|
* Take care that this value is corrent, SSLSession performs no validation
|
||||||
|
* of the hostname.
|
||||||
*/
|
*/
|
||||||
void set_parameters(const IPAddress& ip, const char* hostname = NULL) {
|
void set_parameters(const IPAddress& ip, const char* hostname = NULL) {
|
||||||
// copy the hostname
|
// copy the hostname
|
||||||
|
@ -68,8 +114,16 @@ public:
|
||||||
// check if both values are valid, and if so set valid to true
|
// check if both values are valid, and if so set valid to true
|
||||||
if (m_ip != INADDR_NONE && session_id_len > 0
|
if (m_ip != INADDR_NONE && session_id_len > 0
|
||||||
&& (hostname == NULL || m_hostname)) m_valid_session = true;
|
&& (hostname == NULL || m_hostname)) m_valid_session = true;
|
||||||
|
// else clear
|
||||||
|
else clear_parameters();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief delete the parameters and invalidate the session
|
||||||
|
* Roughly equivalent to this_session = SSLSession(), however
|
||||||
|
* this function preserves the String object, allowing it
|
||||||
|
* to better handle the dynamic memory needed.
|
||||||
|
*/
|
||||||
void clear_parameters() {
|
void clear_parameters() {
|
||||||
// clear the hostname , ip, and valid session flags
|
// clear the hostname , ip, and valid session flags
|
||||||
m_hostname = "";
|
m_hostname = "";
|
||||||
|
@ -77,21 +131,9 @@ public:
|
||||||
m_valid_session = false;
|
m_valid_session = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
SSLSession& operator=(const SSLSession&) = delete;
|
/** @brief returns a pointer to the ::br_ssl_session_parameters component of this class */
|
||||||
|
|
||||||
br_ssl_session_parameters* to_br_session() { return (br_ssl_session_parameters *)this; }
|
br_ssl_session_parameters* to_br_session() { return (br_ssl_session_parameters *)this; }
|
||||||
|
|
||||||
/**
|
|
||||||
* \pre must check isValidSession
|
|
||||||
*/
|
|
||||||
const String& get_hostname() const { return m_hostname; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \pre must check isValidSession
|
|
||||||
*/
|
|
||||||
const IPAddress& get_ip() const { return m_ip; }
|
|
||||||
|
|
||||||
bool is_valid_session() const { return m_valid_session; }
|
|
||||||
private:
|
private:
|
||||||
bool m_valid_session;
|
bool m_valid_session;
|
||||||
// aparently a hostname has a max length of 256 chars. Go figure.
|
// aparently a hostname has a max length of 256 chars. Go figure.
|
||||||
|
|
Loading…
Reference in a new issue