made a lot of progress creating the SSLClient, slow but steady
This commit is contained in:
parent
0d424049f0
commit
093d1fac8b
5 changed files with 178 additions and 21 deletions
|
@ -18,4 +18,67 @@
|
|||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "SSLClient.h"
|
||||
#include "SSLClient.h"
|
||||
|
||||
/** see SSLClient.h */
|
||||
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);
|
||||
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.")
|
||||
// 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");
|
||||
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) {
|
||||
m_print("Failed to initlalize the SSL layer");
|
||||
return 0;
|
||||
}
|
||||
// all good to go! the SSL socket should be up and running
|
||||
m_print("SSL Initialized");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* see SSLClient.h */
|
||||
virtual int SSLClient::connect(const char *host, uint16_t port) {
|
||||
// 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)) {
|
||||
m_print("Failed to connect using m_client");
|
||||
return 0;
|
||||
}
|
||||
// reset the client context, and look for previous sessions
|
||||
br_ssl_client_reset(&sc, host, 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) {
|
||||
m_print("Failed to initlalize the SSL layer");
|
||||
return 0;
|
||||
}
|
||||
// all good to go! the SSL socket should be up and running
|
||||
m_print("SSL Initialized");
|
||||
return 1;
|
||||
}
|
||||
|
||||
virtual size_t SSLClient::write(const uint8_t *buf, size_t size) {
|
||||
// check if the socket is still open and such
|
||||
|
||||
// write to the ssl socket using bearssl, and error check
|
||||
int status = br_sslio_write_all(&m_ioctx, buf, len);
|
||||
if (status < 0 )
|
||||
}
|
|
@ -50,18 +50,22 @@ 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:
|
||||
|
||||
/** Ctor
|
||||
* Creates a new dynamically allocated Client object based on the
|
||||
* one passed to client
|
||||
/**
|
||||
* @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
|
||||
* @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):
|
||||
m_client(client)
|
||||
{
|
||||
}
|
||||
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)
|
||||
, m_debug(debug);
|
||||
|
||||
/** Dtor is implicit since unique_ptr handles it fine */
|
||||
|
||||
|
@ -83,10 +87,50 @@ public:
|
|||
/** functions specific to the EthernetClient which I'll have to override */
|
||||
uint8_t status() const;
|
||||
uint8_t getSocketNumber() const;
|
||||
|
||||
/** functions dealing with read/write that BearSSL will be injected into */
|
||||
virtual int connect(IPAddress ip, uint16_t port);
|
||||
virtual int connect(const char *host, uint16_t port);
|
||||
virtual size_t write(uint8_t);
|
||||
/**
|
||||
* @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)
|
||||
*/
|
||||
virtual int connect(IPAddress ip, uint16_t port = 443);
|
||||
/**
|
||||
* @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)
|
||||
*/
|
||||
virtual int connect(const char *host, uint16_t port = 443);
|
||||
virtual size_t write(uint8_t b) { return write(&b, 1); }
|
||||
virtual size_t write(const uint8_t *buf, size_t size);
|
||||
virtual int available();
|
||||
virtual int read();
|
||||
|
@ -100,8 +144,26 @@ public:
|
|||
C& getClient() { return m_client; }
|
||||
|
||||
private:
|
||||
/** @brief debugging print function, only prints if m_debug is true */
|
||||
template<type T>
|
||||
constexpr void m_print(const T str) {
|
||||
if (m_debug) {
|
||||
Serial.print("SSLClient: ");
|
||||
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);
|
||||
// create a copy of the client
|
||||
C m_client;
|
||||
// store pointers to the trust anchors
|
||||
// should not be computed at runtime
|
||||
constexpr br_x509_trust_anchor *m_trust_anchors;
|
||||
constexpr size_t m_trust_anchors_num;
|
||||
// store whether to enable debug logging
|
||||
constexpr bool m_debug;
|
||||
// store the context values required for SSL
|
||||
br_ssl_client_context m_sslctx;
|
||||
br_x509_minimal_context m_x509ctx;
|
||||
|
|
|
@ -49,18 +49,18 @@
|
|||
*/
|
||||
|
||||
void
|
||||
TLS12_only_profile(br_ssl_client_context *cc
|
||||
br_client_init_TLS12_only(br_ssl_client_context *cc,
|
||||
br_x509_minimal_context *xc,
|
||||
const br_x509_trust_anchor *trust_anchors, size_t trust_anchors_num)
|
||||
{
|
||||
/*
|
||||
* The "full" profile supports all implemented cipher suites.
|
||||
* The TLS1.2 profile supports widely used implemented cipher suites.
|
||||
*
|
||||
* Rationale for suite order, from most important to least
|
||||
* important rule:
|
||||
*
|
||||
* -- Only support TLS 1.2
|
||||
* -- Don't support RSA and 3DES as they are weaker protocols
|
||||
* -- Don't support RSA and 3DES as primary encryption as they are weaker protocols
|
||||
* -- Try to have Forward Secrecy (ECDHE suite) if possible.
|
||||
* -- When not using Forward Secrecy, ECDH key exchange is
|
||||
* better than RSA key exchange (slightly more expensive on the
|
||||
|
|
|
@ -48,8 +48,8 @@ br_sslio_init(br_sslio_context *ctx,
|
|||
* combination of both (the combination matches either). When a match is
|
||||
* achieved, this function returns 0. On error, it returns -1.
|
||||
*/
|
||||
static int
|
||||
run_until(br_sslio_context *ctx, unsigned target)
|
||||
int
|
||||
br_run_until(br_sslio_context *ctx, unsigned target)
|
||||
{
|
||||
for (;;) {
|
||||
unsigned state;
|
||||
|
@ -152,7 +152,7 @@ br_sslio_read(br_sslio_context *ctx, void *dst, size_t len)
|
|||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (run_until(ctx, BR_SSL_RECVAPP) < 0) {
|
||||
if (br_run_until(ctx, BR_SSL_RECVAPP) < 0) {
|
||||
return -1;
|
||||
}
|
||||
buf = br_ssl_engine_recvapp_buf(ctx->engine, &alen);
|
||||
|
@ -194,7 +194,7 @@ br_sslio_write(br_sslio_context *ctx, const void *src, size_t len)
|
|||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (run_until(ctx, BR_SSL_SENDAPP) < 0) {
|
||||
if (br_run_until(ctx, BR_SSL_SENDAPP) < 0) {
|
||||
return -1;
|
||||
}
|
||||
buf = br_ssl_engine_sendapp_buf(ctx->engine, &alen);
|
||||
|
@ -238,7 +238,7 @@ br_sslio_flush(br_sslio_context *ctx)
|
|||
* first sent down the wire before considering anything else.
|
||||
*/
|
||||
br_ssl_engine_flush(ctx->engine, 0);
|
||||
return run_until(ctx, BR_SSL_SENDAPP | BR_SSL_RECVAPP);
|
||||
return br_run_until(ctx, BR_SSL_SENDAPP | BR_SSL_RECVAPP);
|
||||
}
|
||||
|
||||
/* see bearssl_ssl.h */
|
||||
|
@ -252,7 +252,7 @@ br_sslio_close(br_sslio_context *ctx)
|
|||
*/
|
||||
size_t len;
|
||||
|
||||
run_until(ctx, BR_SSL_RECVAPP);
|
||||
br_run_until(ctx, BR_SSL_RECVAPP);
|
||||
if (br_ssl_engine_recvapp_buf(ctx->engine, &len) != NULL) {
|
||||
br_ssl_engine_recvapp_ack(ctx->engine, len);
|
||||
}
|
||||
|
|
|
@ -2735,6 +2735,24 @@ void br_ssl_client_init_full(br_ssl_client_context *cc,
|
|||
br_x509_minimal_context *xc,
|
||||
const br_x509_trust_anchor *trust_anchors, size_t trust_anchors_num);
|
||||
|
||||
/**
|
||||
* \brief SSL client profile: TLS1.2 minus weak ciphers
|
||||
*
|
||||
* This function initialises the provided SSL client context with
|
||||
* most (see brief) supported algorithms and cipher suites. It also initialises
|
||||
* a companion X.509 validation engine with most supported algorithms,
|
||||
* and the provided trust anchors; the X.509 engine will be used by
|
||||
* the client context to validate the server's certificate.
|
||||
*
|
||||
* \param cc client context to initialise.
|
||||
* \param xc X.509 validation context to initialise.
|
||||
* \param trust_anchors trust anchors to use.
|
||||
* \param trust_anchors_num number of trust anchors.
|
||||
*/
|
||||
void br_client_init_TLS12_only(br_ssl_client_context *cc,
|
||||
br_x509_minimal_context *xc,
|
||||
const br_x509_trust_anchor *trust_anchors, size_t trust_anchors_num);
|
||||
|
||||
/**
|
||||
* \brief Clear the complete contents of a SSL client context.
|
||||
*
|
||||
|
@ -4136,6 +4154,20 @@ int br_sslio_flush(br_sslio_context *cc);
|
|||
*/
|
||||
int br_sslio_close(br_sslio_context *cc);
|
||||
|
||||
/*
|
||||
* Run the engine, until the specified target state is achieved, or
|
||||
* an error occurs. The target state is SENDAPP, RECVAPP, or the
|
||||
* combination of both (the combination matches either). When a match is
|
||||
* achieved, this function returns 0. On error, it returns -1.
|
||||
*
|
||||
* Static function made public since we would like to be able to
|
||||
* initialize the ssl socket in a single function
|
||||
*
|
||||
* \return 0 on success, or -1 on error.
|
||||
*/
|
||||
int
|
||||
br_run_until(br_sslio_context *ctx, unsigned target);
|
||||
|
||||
/* ===================================================================== */
|
||||
|
||||
/*
|
||||
|
|
Loading…
Reference in a new issue