From 093d1fac8b83a160502e98402e8d4ebe321edba3 Mon Sep 17 00:00:00 2001 From: Noah Laptop Date: Thu, 21 Feb 2019 11:45:52 -0800 Subject: [PATCH] made a lot of progress creating the SSLClient, slow but steady --- src/SSLClient.cpp | 65 +++++++++++++++++++++++- src/SSLClient.h | 84 +++++++++++++++++++++++++++----- src/bearssl/TLS12_only_profile.c | 6 +-- src/bearssl/src/ssl/ssl_io.c | 12 ++--- src/bearssl_ssl.h | 32 ++++++++++++ 5 files changed, 178 insertions(+), 21 deletions(-) diff --git a/src/SSLClient.cpp b/src/SSLClient.cpp index 85c1e45..045bfda 100644 --- a/src/SSLClient.cpp +++ b/src/SSLClient.cpp @@ -18,4 +18,67 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include "SSLClient.h" \ No newline at end of file +#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 ) +} \ No newline at end of file diff --git a/src/SSLClient.h b/src/SSLClient.h index 8b3e389..d14135d 100644 --- a/src/SSLClient.h +++ b/src/SSLClient.h @@ -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 + 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; diff --git a/src/bearssl/TLS12_only_profile.c b/src/bearssl/TLS12_only_profile.c index b58ee01..4400b5a 100644 --- a/src/bearssl/TLS12_only_profile.c +++ b/src/bearssl/TLS12_only_profile.c @@ -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 diff --git a/src/bearssl/src/ssl/ssl_io.c b/src/bearssl/src/ssl/ssl_io.c index 1952615..e929228 100644 --- a/src/bearssl/src/ssl/ssl_io.c +++ b/src/bearssl/src/ssl/ssl_io.c @@ -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); } diff --git a/src/bearssl_ssl.h b/src/bearssl_ssl.h index 8c8c86b..d28405a 100644 --- a/src/bearssl_ssl.h +++ b/src/bearssl_ssl.h @@ -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); + /* ===================================================================== */ /*