// ----------------------------------------------------------------------------- // WiFiClientSecure validation helpers // ----------------------------------------------------------------------------- #pragma once #include "../espurna.h" #if SECURE_CLIENT != SECURE_CLIENT_NONE #include "../ntp.h" #if SECURE_CLIENT == SECURE_CLIENT_BEARSSL #include "ntp_timelib.h" #include #elif SECURE_CLIENT == SECURE_CLIENT_AXTLS #include #endif namespace SecureClientHelpers { using host_callback_f = std::function; using check_callback_f = std::function; using fp_callback_f = std::function; using cert_callback_f = std::function; using mfln_callback_f = std::function; // TODO: workaround for `multiple definition of `SecureClientHelpers::_secureClientCheckAsString(int);' inline const char * _secureClientCheckAsString(int check) { switch (check) { case SECURE_CLIENT_CHECK_NONE: return "no validation"; case SECURE_CLIENT_CHECK_FINGERPRINT: return "fingerprint validation"; case SECURE_CLIENT_CHECK_CA: return "CA validation"; default: return "unknown"; } } #if SECURE_CLIENT == SECURE_CLIENT_AXTLS using SecureClientClass = axTLS::WiFiClientSecure; struct SecureClientConfig { SecureClientConfig(const char* tag, host_callback_f host_cb, check_callback_f check_cb, fp_callback_f fp_cb, bool debug = false) : tag(tag), on_host(host_cb), on_check(check_cb), on_fingerprint(fp_cb), debug(debug) {} String tag; host_callback_f on_host; check_callback_f on_check; fp_callback_f on_fingerprint; bool debug; }; struct SecureClientChecks { SecureClientChecks(SecureClientConfig& config) : config(config) {} int getCheck() { return (config.on_check) ? config.on_check() : (SECURE_CLIENT_CHECK); } bool beforeConnected(SecureClientClass& client) { return true; } // Special condition for legacy client! // Otherwise, we are required to connect twice. And it is deemed broken & deprecated anyways... bool afterConnected(SecureClientClass& client) { bool result = false; int check = getCheck(); if(config.debug) { DEBUG_MSG_P(PSTR("[%s] Using SSL check type: %s\n"), config.tag.c_str(), _secureClientCheckAsString(check)); } if (check == SECURE_CLIENT_CHECK_NONE) { if (config.debug) DEBUG_MSG_P(PSTR("[%s] !!! Secure connection will not be validated !!!\n"), config.tag.c_str()); result = true; } else if (check == SECURE_CLIENT_CHECK_FINGERPRINT) { if (config.on_fingerprint) { char _buffer[60] = {0}; if (config.on_fingerprint && config.on_host && sslFingerPrintChar(config.on_fingerprint().c_str(), _buffer)) { result = client.verify(_buffer, config.on_host().c_str()); } if (!result) DEBUG_MSG_P(PSTR("[%s] Wrong fingerprint, cannot connect\n"), config.tag.c_str()); } } else if (check == SECURE_CLIENT_CHECK_CA) { if (config.debug) DEBUG_MSG_P(PSTR("[%s] CA verification is not supported with axTLS client\n"), config.tag.c_str()); } return result; } SecureClientConfig& config; bool debug; }; #endif // SECURE_CLIENT_AXTLS #if SECURE_CLIENT == SECURE_CLIENT_BEARSSL using SecureClientClass = BearSSL::WiFiClientSecure; struct SecureClientConfig { SecureClientConfig(const char* tag, check_callback_f check_cb, cert_callback_f cert_cb, fp_callback_f fp_cb, mfln_callback_f mfln_cb, bool debug = false) : tag(tag), on_check(check_cb), on_certificate(cert_cb), on_fingerprint(fp_cb), on_mfln(mfln_cb), debug(debug) {} String tag; check_callback_f on_check; cert_callback_f on_certificate; fp_callback_f on_fingerprint; mfln_callback_f on_mfln; bool debug; }; struct SecureClientChecks { SecureClientChecks(SecureClientConfig& config) : config(config) {} int getCheck() { return (config.on_check) ? config.on_check() : (SECURE_CLIENT_CHECK); } bool prepareMFLN(SecureClientClass& client) { const uint16_t requested_mfln = (config.on_mfln) ? config.on_mfln() : (SECURE_CLIENT_MFLN); bool result = false; switch (requested_mfln) { // default, do nothing case 0: result = true; break; // match valid sizes only case 512: case 1024: case 2048: case 4096: { client.setBufferSizes(requested_mfln, requested_mfln); result = true; if (config.debug) { DEBUG_MSG_P(PSTR("[%s] MFLN buffer size set to %u\n"), config.tag.c_str(), requested_mfln); } break; } default: { if (config.debug) { DEBUG_MSG_P(PSTR("[%s] Warning: MFLN buffer size must be one of 512, 1024, 2048 or 4096\n"), config.tag.c_str()); } } } return result; } bool beforeConnected(SecureClientClass& client) { int check = getCheck(); bool settime = (check == SECURE_CLIENT_CHECK_CA); if(config.debug) { DEBUG_MSG_P(PSTR("[%s] Using SSL check type: %s\n"), config.tag.c_str(), _secureClientCheckAsString(check)); } if (!ntpSynced() && settime) { if (config.debug) DEBUG_MSG_P(PSTR("[%s] Time not synced! Cannot use CA validation\n"), config.tag.c_str()); return false; } prepareMFLN(client); if (check == SECURE_CLIENT_CHECK_NONE) { if (config.debug) DEBUG_MSG_P(PSTR("[%s] !!! Secure connection will not be validated !!!\n"), config.tag.c_str()); client.setInsecure(); } else if (check == SECURE_CLIENT_CHECK_FINGERPRINT) { uint8_t _buffer[20] = {0}; if (config.on_fingerprint && sslFingerPrintArray(config.on_fingerprint().c_str(), _buffer)) { client.setFingerprint(_buffer); } } else if (check == SECURE_CLIENT_CHECK_CA) { #if NTP_LEGACY_SUPPORT client.setX509Time(ntpLocal2UTC(now())); #else client.setX509Time(now()); #endif if (!certs.getCount()) { if (config.on_certificate) certs.append(config.on_certificate()); } client.setTrustAnchors(&certs); } return true; } bool afterConnected(SecureClientClass&) { return true; } bool debug; SecureClientConfig& config; BearSSL::X509List certs; }; #endif // SECURE_CLIENT_BEARSSL class SecureClient { public: SecureClient(SecureClientConfig& config) : _config(config), _checks(_config), _client(std::make_unique()) {} bool afterConnected() { return _checks.afterConnected(get()); } bool beforeConnected() { return _checks.beforeConnected(get()); } SecureClientClass& get() { return *_client.get(); } private: SecureClientConfig _config; SecureClientChecks _checks; std::unique_ptr _client; }; }; using SecureClientConfig = SecureClientHelpers::SecureClientConfig; using SecureClientChecks = SecureClientHelpers::SecureClientChecks; using SecureClient = SecureClientHelpers::SecureClient; #endif // SECURE_CLIENT != SECURE_CLIENT_NONE