made a lot of progress creating the SSLClient, slow but steady

This commit is contained in:
Noah Laptop 2019-02-21 11:45:52 -08:00
parent 0d424049f0
commit 093d1fac8b
5 changed files with 178 additions and 21 deletions

View file

@ -19,3 +19,66 @@
*/ */
#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 )
}

View file

@ -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!"); static_assert(std::is_function(decltype(C::status))::value, "C must have a status() function!");
public: public:
/**
/** Ctor * @brief copies the client object and initializes SSL contexts for bearSSL
* Creates a new dynamically allocated Client object based on the
* one passed to client
* 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.
* @param client the (Ethernet)client object * @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): 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_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 */ /** 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 */ /** functions specific to the EthernetClient which I'll have to override */
uint8_t status() const; uint8_t status() const;
uint8_t getSocketNumber() const; uint8_t getSocketNumber() const;
/** functions dealing with read/write that BearSSL will be injected into */ /** 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); * @brief Connect over SSL to a host specified by an ip address
virtual size_t write(uint8_t); *
* 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 size_t write(const uint8_t *buf, size_t size);
virtual int available(); virtual int available();
virtual int read(); virtual int read();
@ -100,8 +144,26 @@ public:
C& getClient() { return m_client; } C& getClient() { return m_client; }
private: 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 // create a copy of the client
C m_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 // store the context values required for SSL
br_ssl_client_context m_sslctx; br_ssl_client_context m_sslctx;
br_x509_minimal_context m_x509ctx; br_x509_minimal_context m_x509ctx;

View file

@ -49,18 +49,18 @@
*/ */
void void
TLS12_only_profile(br_ssl_client_context *cc br_client_init_TLS12_only(br_ssl_client_context *cc,
br_x509_minimal_context *xc, br_x509_minimal_context *xc,
const br_x509_trust_anchor *trust_anchors, size_t trust_anchors_num) 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 * Rationale for suite order, from most important to least
* important rule: * important rule:
* *
* -- Only support TLS 1.2 * -- 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. * -- Try to have Forward Secrecy (ECDHE suite) if possible.
* -- When not using Forward Secrecy, ECDH key exchange is * -- When not using Forward Secrecy, ECDH key exchange is
* better than RSA key exchange (slightly more expensive on the * better than RSA key exchange (slightly more expensive on the

View file

@ -48,8 +48,8 @@ br_sslio_init(br_sslio_context *ctx,
* combination of both (the combination matches either). When a match is * combination of both (the combination matches either). When a match is
* achieved, this function returns 0. On error, it returns -1. * achieved, this function returns 0. On error, it returns -1.
*/ */
static int int
run_until(br_sslio_context *ctx, unsigned target) br_run_until(br_sslio_context *ctx, unsigned target)
{ {
for (;;) { for (;;) {
unsigned state; unsigned state;
@ -152,7 +152,7 @@ br_sslio_read(br_sslio_context *ctx, void *dst, size_t len)
if (len == 0) { if (len == 0) {
return 0; return 0;
} }
if (run_until(ctx, BR_SSL_RECVAPP) < 0) { if (br_run_until(ctx, BR_SSL_RECVAPP) < 0) {
return -1; return -1;
} }
buf = br_ssl_engine_recvapp_buf(ctx->engine, &alen); 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) { if (len == 0) {
return 0; return 0;
} }
if (run_until(ctx, BR_SSL_SENDAPP) < 0) { if (br_run_until(ctx, BR_SSL_SENDAPP) < 0) {
return -1; return -1;
} }
buf = br_ssl_engine_sendapp_buf(ctx->engine, &alen); 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. * first sent down the wire before considering anything else.
*/ */
br_ssl_engine_flush(ctx->engine, 0); 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 */ /* see bearssl_ssl.h */
@ -252,7 +252,7 @@ br_sslio_close(br_sslio_context *ctx)
*/ */
size_t len; 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) { if (br_ssl_engine_recvapp_buf(ctx->engine, &len) != NULL) {
br_ssl_engine_recvapp_ack(ctx->engine, len); br_ssl_engine_recvapp_ack(ctx->engine, len);
} }

View file

@ -2735,6 +2735,24 @@ void br_ssl_client_init_full(br_ssl_client_context *cc,
br_x509_minimal_context *xc, br_x509_minimal_context *xc,
const br_x509_trust_anchor *trust_anchors, size_t trust_anchors_num); 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. * \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); 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);
/* ===================================================================== */ /* ===================================================================== */
/* /*