diff --git a/CMakeLists.txt b/CMakeLists.txt index bba8b88b146..cffa02a3c39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,6 +99,7 @@ set(ARDUINO_ALL_LIBRARIES SimpleBLE SPIFFS SPI + SSLClient Ticker Update USB @@ -175,6 +176,10 @@ set(ARDUINO_LIBRARY_SPIFFS_SRCS libraries/SPIFFS/src/SPIFFS.cpp) set(ARDUINO_LIBRARY_SPI_SRCS libraries/SPI/src/SPI.cpp) +set(ARDUINO_LIBRARY_SSLClient_SRCS + libraries/SSLClient/src/ssl_client.cpp + libraries/SSLClient/src/SSLClient.cpp) + set(ARDUINO_LIBRARY_Ticker_SRCS libraries/Ticker/src/Ticker.cpp) set(ARDUINO_LIBRARY_Update_SRCS diff --git a/libraries/SSLClient/README.md b/libraries/SSLClient/README.md new file mode 100644 index 00000000000..15c4a35d8b9 --- /dev/null +++ b/libraries/SSLClient/README.md @@ -0,0 +1,83 @@ +SSLClient +========= + +The SSLClient class implements support for secure connections using TLS (SSL). +It inherits from Client and thus implements a superset of that class' interface. +There are three ways to establish a secure connection using the SSLClient class: +using a root certificate authority (CA) cert, using a root CA cert plus a client cert and key, +and using a pre-shared key (PSK). + +Using a root certificate authority cert +--------------------------------------- +This method authenticates the server and negotiates an encrypted connection. +It is the same functionality as implemented in your web browser when you connect to HTTPS sites. + +If you are accessing your own server: +- Generate a root certificate for your own certificate authority +- Generate a cert & private key using your root certificate ("self-signed cert") for your server + +If you are accessing a public server: +- Obtain the cert of the public CA that signed that server's cert +Then: +- In SSLClient use setCACert (or the appropriate connect method) to set the root cert of your + CA or of the public CA +- When SSLClient connects to the target server it uses the CA cert to verify the certificate + presented by the server, and then negotiates encryption for the connection + +Please see the SSLClient example. + +Using a root CA cert and client cert/keys +----------------------------------------- +This method authenticates the server and additionally also authenticates +the client to the server, then negotiates an encrypted connection. + +- Follow steps above +- Using your root CA generate cert/key for your client +- Register the keys with the server you will be accessing so the server can authenticate your client +- In SSLClient use setCACert (or the appropriate connect method) to set the root cert of your + CA or of the public CA, this is used to authenticate the server +- In SSLClient use setCertificate, and setPrivateKey (or the appropriate connect method) to + set your client's cert & key, this will be used to authenticate your client to the server +- When SSLClient connects to the target server it uses the CA cert to verify the certificate + presented by the server, it will use the cert/key to authenticate your client to the server, and + it will then negotiate encryption for the connection + +Using Pre-Shared Keys (PSK) +--------------------------- + +TLS supports authentication and encryption using a pre-shared key (i.e. a key that both client and +server know) as an alternative to the public key cryptography commonly used on the web for HTTPS. +PSK is starting to be used for MQTT, e.g. in mosquitto, to simplify the set-up and avoid having to +go through the whole CA, cert, and private key process. + +A pre-shared key is a binary string of up to 32 bytes and is commonly represented in hex form. In +addition to the key, clients can also present an id and typically the server allows a different key +to be associated with each client id. In effect this is very similar to username and password pairs, +except that unlike a password the key is not directly transmitted to the server, thus a connection to a +malicious server does not divulge the password. Plus the server is also authenticated to the client. + +To use PSK: +- Generate a random hex string (generating an MD5 or SHA for some file is one way to do this) +- Come up with a string id for your client and configure your server to accept the id/key pair +- In SSLClient use setPreSharedKey (or the appropriate connect method) to + set the id/key combo +- When SSLClient connects to the target server it uses the id/key combo to authenticate the + server (it must prove that it has the key too), authenticate the client and then negotiate + encryption for the connection + +Please see the SSLClientPSK example. + +Specifying the ALPN Protocol +---------------------------- + +Application-Layer Protocol Negotiation (ALPN) is a Transport Layer Security (TLS) extension that allows +the application layer to negotiate which protocol should be performed over a secure connection in a manner +that avoids additional round trips and which is independent of the application-layer protocols. + +For example, this is used with AWS IoT Custom Authorizers where an MQTT client must set the ALPN protocol to ```mqtt```: + +``` +const char *aws_protos[] = {"mqtt", NULL}; +... +sslClient.setAlpnProtocols(aws_protos); +``` diff --git a/libraries/SSLClient/examples/WiFiClientInsecure/WiFiClientInsecure.ino b/libraries/SSLClient/examples/WiFiClientInsecure/WiFiClientInsecure.ino new file mode 100644 index 00000000000..8d089d3db69 --- /dev/null +++ b/libraries/SSLClient/examples/WiFiClientInsecure/WiFiClientInsecure.ino @@ -0,0 +1,63 @@ +#include +#include + +const char* ssid = "your-ssid"; // your network SSID (name of wifi network) +const char* password = "your-password"; // your network password + +const char* server = "www.howsmyssl.com"; // Server URL + +WiFiClient wificlient; +SSLClient client(wificlient); + +void setup() { + //Initialize serial and wait for port to open: + Serial.begin(115200); + delay(100); + + Serial.print("Attempting to connect to SSID: "); + Serial.println(ssid); + WiFi.begin(ssid, password); + + // attempt to connect to Wifi network: + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + // wait 1 second for re-trying + delay(1000); + } + + Serial.print("Connected to "); + Serial.println(ssid); + + Serial.println("\nStarting connection to server..."); + client.setInsecure();//skip verification + if (!client.connect(server, 443)) + Serial.println("Connection failed!"); + else { + Serial.println("Connected to server!"); + // Make a HTTP request: + client.println("GET https://www.howsmyssl.com/a/check HTTP/1.0"); + client.println("Host: www.howsmyssl.com"); + client.println("Connection: close"); + client.println(); + + while (client.connected()) { + String line = client.readStringUntil('\n'); + if (line == "\r") { + Serial.println("headers received"); + break; + } + } + // if there are incoming bytes available + // from the server, read them and print them: + while (client.available()) { + char c = client.read(); + Serial.write(c); + } + + client.stop(); + } +} + +void loop() { + // do nothing +} diff --git a/libraries/SSLClient/examples/WiFiClientPSK/WiFiClientPSK.ino b/libraries/SSLClient/examples/WiFiClientPSK/WiFiClientPSK.ino new file mode 100644 index 00000000000..bcbe3f2d6c0 --- /dev/null +++ b/libraries/SSLClient/examples/WiFiClientPSK/WiFiClientPSK.ino @@ -0,0 +1,87 @@ +/* + Wifi secure connection example for ESP32 using a pre-shared key (PSK) + This is useful with MQTT servers instead of using a self-signed cert, tested with mosquitto. + Running on TLS 1.2 using mbedTLS + + To test run a test server using: openssl s_server -accept 8443 -psk 1a2b3c4d -nocert + It will show the http request made, but there's no easy way to send a reply back... + + 2017 - Evandro Copercini - Apache 2.0 License. + 2018 - Adapted for PSK by Thorsten von Eicken +*/ + +#include +#include + +#if 0 +const char* ssid = "your-ssid"; // your network SSID (name of wifi network) +const char* password = "your-password"; // your network password +#else +const char* ssid = "test"; // your network SSID (name of wifi network) +const char* password = "securetest"; // your network password +#endif + +//const char* server = "server.local"; // Server hostname +const IPAddress server = IPAddress(192, 168, 0, 14); // Server IP address +const int port = 8443; // server's port (8883 for MQTT) + +const char* pskIdent = "Client_identity"; // PSK identity (sometimes called key hint) +const char* psKey = "1a2b3c4d"; // PSK Key (must be hex string without 0x) + +WiFiClient wificlient; +SSLClient client(wificlient); + +void setup() { + //Initialize serial and wait for port to open: + Serial.begin(115200); + delay(100); + + Serial.print("Attempting to connect to SSID: "); + Serial.println(ssid); + WiFi.begin(ssid, password); + + // attempt to connect to Wifi network: + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + // wait 1 second for re-trying + delay(1000); + } + + Serial.print("Connected to "); + Serial.println(ssid); + + client.setPreSharedKey(pskIdent, psKey); + + Serial.println("\nStarting connection to server..."); + if (!client.connect(server, port)) + Serial.println("Connection failed!"); + else { + Serial.println("Connected to server!"); + // Make a HTTP request: + client.println("GET /a/check HTTP/1.0"); + client.print("Host: "); + client.println(server); + client.println("Connection: close"); + client.println(); + + while (client.connected()) { + String line = client.readStringUntil('\n'); + if (line == "\r") { + Serial.println("headers received"); + break; + } + } + // if there are incoming bytes available + // from the server, read them and print them: + while (client.available()) { + char c = client.read(); + Serial.write(c); + } + + client.stop(); + } +} + +void loop() { + // do nothing +} diff --git a/libraries/SSLClient/examples/WiFiClientSecure/WiFiClientSecure.ino b/libraries/SSLClient/examples/WiFiClientSecure/WiFiClientSecure.ino new file mode 100644 index 00000000000..d921d9890be --- /dev/null +++ b/libraries/SSLClient/examples/WiFiClientSecure/WiFiClientSecure.ino @@ -0,0 +1,105 @@ +/* + Wifi secure connection example for ESP32 + Running on TLS 1.2 using mbedTLS + Suporting the following chipersuites: + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384","TLS_DHE_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_ECDSA_WITH_AES_256_CCM","TLS_DHE_RSA_WITH_AES_256_CCM","TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384","TLS_DHE_RSA_WITH_AES_256_CBC_SHA256","TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA","TLS_DHE_RSA_WITH_AES_256_CBC_SHA","TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8","TLS_DHE_RSA_WITH_AES_256_CCM_8","TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384","TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384","TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256","TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA","TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256","TLS_DHE_RSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_ECDSA_WITH_AES_128_CCM","TLS_DHE_RSA_WITH_AES_128_CCM","TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256","TLS_DHE_RSA_WITH_AES_128_CBC_SHA256","TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA","TLS_DHE_RSA_WITH_AES_128_CBC_SHA","TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8","TLS_DHE_RSA_WITH_AES_128_CCM_8","TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA","TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA","TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA","TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA","TLS_DHE_PSK_WITH_AES_256_GCM_SHA384","TLS_DHE_PSK_WITH_AES_256_CCM","TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384","TLS_DHE_PSK_WITH_AES_256_CBC_SHA384","TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA","TLS_DHE_PSK_WITH_AES_256_CBC_SHA","TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384","TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384","TLS_PSK_DHE_WITH_AES_256_CCM_8","TLS_DHE_PSK_WITH_AES_128_GCM_SHA256","TLS_DHE_PSK_WITH_AES_128_CCM","TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256","TLS_DHE_PSK_WITH_AES_128_CBC_SHA256","TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA","TLS_DHE_PSK_WITH_AES_128_CBC_SHA","TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256","TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256","TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256","TLS_PSK_DHE_WITH_AES_128_CCM_8","TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA","TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA","TLS_RSA_WITH_AES_256_GCM_SHA384","TLS_RSA_WITH_AES_256_CCM","TLS_RSA_WITH_AES_256_CBC_SHA256","TLS_RSA_WITH_AES_256_CBC_SHA","TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384","TLS_ECDH_RSA_WITH_AES_256_CBC_SHA","TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384","TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384","TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA","TLS_RSA_WITH_AES_256_CCM_8","TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256","TLS_RSA_WITH_CAMELLIA_256_CBC_SHA","TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384","TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384","TLS_RSA_WITH_AES_128_GCM_SHA256","TLS_RSA_WITH_AES_128_CCM","TLS_RSA_WITH_AES_128_CBC_SHA256","TLS_RSA_WITH_AES_128_CBC_SHA","TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256","TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256","TLS_ECDH_RSA_WITH_AES_128_CBC_SHA","TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256","TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256","TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA","TLS_RSA_WITH_AES_128_CCM_8","TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_RSA_WITH_CAMELLIA_128_CBC_SHA","TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_RSA_WITH_3DES_EDE_CBC_SHA","TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA","TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA","TLS_RSA_PSK_WITH_AES_256_GCM_SHA384","TLS_RSA_PSK_WITH_AES_256_CBC_SHA384","TLS_RSA_PSK_WITH_AES_256_CBC_SHA","TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384","TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384","TLS_RSA_PSK_WITH_AES_128_GCM_SHA256","TLS_RSA_PSK_WITH_AES_128_CBC_SHA256","TLS_RSA_PSK_WITH_AES_128_CBC_SHA","TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256","TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256","TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA","TLS_PSK_WITH_AES_256_GCM_SHA384","TLS_PSK_WITH_AES_256_CCM","TLS_PSK_WITH_AES_256_CBC_SHA384","TLS_PSK_WITH_AES_256_CBC_SHA","TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384","TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384","TLS_PSK_WITH_AES_256_CCM_8","TLS_PSK_WITH_AES_128_GCM_SHA256","TLS_PSK_WITH_AES_128_CCM","TLS_PSK_WITH_AES_128_CBC_SHA256","TLS_PSK_WITH_AES_128_CBC_SHA","TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256","TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256","TLS_PSK_WITH_AES_128_CCM_8","TLS_PSK_WITH_3DES_EDE_CBC_SHA","TLS_EMPTY_RENEGOTIATION_INFO_SCSV"] + 2017 - Evandro Copercini - Apache 2.0 License. +*/ + +#include +#include + +const char* ssid = "your-ssid"; // your network SSID (name of wifi network) +const char* password = "your-password"; // your network password + +const char* server = "www.howsmyssl.com"; // Server URL + +// www.howsmyssl.com root certificate authority, to verify the server +// change it to your server root CA +// SHA1 fingerprint is broken now! + +const char* test_root_ca= \ + "-----BEGIN CERTIFICATE-----\n" \ + "MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/\n" \ + "MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n" \ + "DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow\n" \ + "PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD\n" \ + "Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\n" \ + "AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O\n" \ + "rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq\n" \ + "OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b\n" \ + "xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw\n" \ + "7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD\n" \ + "aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV\n" \ + "HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG\n" \ + "SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69\n" \ + "ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr\n" \ + "AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz\n" \ + "R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5\n" \ + "JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo\n" \ + "Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ\n" \ + "-----END CERTIFICATE-----\n"; + +// You can use x.509 client certificates if you want +//const char* test_client_key = ""; //to verify the client +//const char* test_client_cert = ""; //to verify the client + + +WiFiClient wificlient; +SSLClient client(wificlient); + +void setup() { + //Initialize serial and wait for port to open: + Serial.begin(115200); + delay(100); + + Serial.print("Attempting to connect to SSID: "); + Serial.println(ssid); + WiFi.begin(ssid, password); + + // attempt to connect to Wifi network: + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + // wait 1 second for re-trying + delay(1000); + } + + Serial.print("Connected to "); + Serial.println(ssid); + + client.setCACert(test_root_ca); + //client.setCertificate(test_client_cert); // for client verification + //client.setPrivateKey(test_client_key); // for client verification + + Serial.println("\nStarting connection to server..."); + if (!client.connect(server, 443)) + Serial.println("Connection failed!"); + else { + Serial.println("Connected to server!"); + // Make a HTTP request: + client.println("GET https://www.howsmyssl.com/a/check HTTP/1.0"); + client.println("Host: www.howsmyssl.com"); + client.println("Connection: close"); + client.println(); + + while (client.connected()) { + String line = client.readStringUntil('\n'); + if (line == "\r") { + Serial.println("headers received"); + break; + } + } + // if there are incoming bytes available + // from the server, read them and print them: + while (client.available()) { + char c = client.read(); + Serial.write(c); + } + + client.stop(); + } +} + +void loop() { + // do nothing +} diff --git a/libraries/SSLClient/examples/WiFiClientSecureEnterprise/WiFiClientSecureEnterprise.ino b/libraries/SSLClient/examples/WiFiClientSecureEnterprise/WiFiClientSecureEnterprise.ino new file mode 100644 index 00000000000..a950b1f3945 --- /dev/null +++ b/libraries/SSLClient/examples/WiFiClientSecureEnterprise/WiFiClientSecureEnterprise.ino @@ -0,0 +1,117 @@ +/*|-----------------------------------------------------------|*/ +/*|WORKING EXAMPLE FOR HTTPS CONNECTION |*/ +/*|Author: Bc. Martin Chlebovec |*/ +/*|Technical University of Košice |*/ +/*|TESTED BOARDS: Devkit v1 DOIT, Devkitc v4 |*/ +/*|CORE: 0.9x, 1.0.0, 1.0.1 tested, working (newer not tested)|*/ +/*|Supported methods: PEAP + MsCHAPv2, EAP-TTLS + MsCHAPv2 |*/ +/*|-----------------------------------------------------------|*/ + +#include +#include +#include "esp_wpa2.h" +#include +#define EAP_ANONYMOUS_IDENTITY "anonymous@example.com" //anonymous identity +#define EAP_IDENTITY "id@example.com" //user identity +#define EAP_PASSWORD "password" //eduroam user password +const char* ssid = "eduroam"; // eduroam SSID +const char* host = "arduino.php5.sk"; //external server domain for HTTPS connection +int counter = 0; +const char* test_root_ca = \ + "-----BEGIN CERTIFICATE-----\n" \ + "MIIEsTCCA5mgAwIBAgIQCKWiRs1LXIyD1wK0u6tTSTANBgkqhkiG9w0BAQsFADBh\n" \ + "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" \ + "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n" \ + "QTAeFw0xNzExMDYxMjIzMzNaFw0yNzExMDYxMjIzMzNaMF4xCzAJBgNVBAYTAlVT\n" \ + "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n" \ + "b20xHTAbBgNVBAMTFFJhcGlkU1NMIFJTQSBDQSAyMDE4MIIBIjANBgkqhkiG9w0B\n" \ + "AQEFAAOCAQ8AMIIBCgKCAQEA5S2oihEo9nnpezoziDtx4WWLLCll/e0t1EYemE5n\n" \ + "+MgP5viaHLy+VpHP+ndX5D18INIuuAV8wFq26KF5U0WNIZiQp6mLtIWjUeWDPA28\n" \ + "OeyhTlj9TLk2beytbtFU6ypbpWUltmvY5V8ngspC7nFRNCjpfnDED2kRyJzO8yoK\n" \ + "MFz4J4JE8N7NA1uJwUEFMUvHLs0scLoPZkKcewIRm1RV2AxmFQxJkdf7YN9Pckki\n" \ + "f2Xgm3b48BZn0zf0qXsSeGu84ua9gwzjzI7tbTBjayTpT+/XpWuBVv6fvarI6bik\n" \ + "KB859OSGQuw73XXgeuFwEPHTIRoUtkzu3/EQ+LtwznkkdQIDAQABo4IBZjCCAWIw\n" \ + "HQYDVR0OBBYEFFPKF1n8a8ADIS8aruSqqByCVtp1MB8GA1UdIwQYMBaAFAPeUDVW\n" \ + "0Uy7ZvCj4hsbw5eyPdFVMA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEF\n" \ + "BQcDAQYIKwYBBQUHAwIwEgYDVR0TAQH/BAgwBgEB/wIBADA0BggrBgEFBQcBAQQo\n" \ + "MCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBCBgNVHR8E\n" \ + "OzA5MDegNaAzhjFodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9i\n" \ + "YWxSb290Q0EuY3JsMGMGA1UdIARcMFowNwYJYIZIAYb9bAECMCowKAYIKwYBBQUH\n" \ + "AgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCwYJYIZIAYb9bAEBMAgG\n" \ + "BmeBDAECATAIBgZngQwBAgIwDQYJKoZIhvcNAQELBQADggEBAH4jx/LKNW5ZklFc\n" \ + "YWs8Ejbm0nyzKeZC2KOVYR7P8gevKyslWm4Xo4BSzKr235FsJ4aFt6yAiv1eY0tZ\n" \ + "/ZN18bOGSGStoEc/JE4ocIzr8P5Mg11kRYHbmgYnr1Rxeki5mSeb39DGxTpJD4kG\n" \ + "hs5lXNoo4conUiiJwKaqH7vh2baryd8pMISag83JUqyVGc2tWPpO0329/CWq2kry\n" \ + "qv66OSMjwulUz0dXf4OHQasR7CNfIr+4KScc6ABlQ5RDF86PGeE6kdwSQkFiB/cQ\n" \ + "ysNyq0jEDQTkfa2pjmuWtMCNbBnhFXBYejfubIhaUbEv2FOQB3dCav+FPg5eEveX\n" \ + "TVyMnGo=\n" \ + "-----END CERTIFICATE-----\n"; +// You can use x.509 client certificates if you want +//const char* test_client_key = ""; //to verify the client +//const char* test_client_cert = ""; //to verify the client +WiFiClient wificlient; +SSLClient client(wificlient); +void setup() { + Serial.begin(115200); + delay(10); + Serial.println(); + Serial.print("Connecting to network: "); + Serial.println(ssid); + WiFi.disconnect(true); //disconnect form wifi to set new wifi connection + WiFi.mode(WIFI_STA); //init wifi mode + esp_wifi_sta_wpa2_ent_set_identity((uint8_t *)EAP_ANONYMOUS_IDENTITY, strlen(EAP_ANONYMOUS_IDENTITY)); //provide identity + esp_wifi_sta_wpa2_ent_set_username((uint8_t *)EAP_IDENTITY, strlen(EAP_IDENTITY)); //provide username + esp_wifi_sta_wpa2_ent_set_password((uint8_t *)EAP_PASSWORD, strlen(EAP_PASSWORD)); //provide password + esp_wifi_sta_wpa2_ent_enable(); + WiFi.begin(ssid); //connect to wifi + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + counter++; + if (counter >= 60) { //after 30 seconds timeout - reset board (on unsucessful connection) + ESP.restart(); + } + } + client.setCACert(test_root_ca); + //client.setCertificate(test_client_cert); // for client verification - certificate + //client.setPrivateKey(test_client_key); // for client verification - private key + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address set: "); + Serial.println(WiFi.localIP()); //print LAN IP +} +void loop() { + if (WiFi.status() == WL_CONNECTED) { //if we are connected to eduroam network + counter = 0; //reset counter + Serial.println("Wifi is still connected with IP: "); + Serial.println(WiFi.localIP()); //inform user about his IP address + } else if (WiFi.status() != WL_CONNECTED) { //if we lost connection, retry + WiFi.begin(ssid); + } + while (WiFi.status() != WL_CONNECTED) { //during lost connection, print dots + delay(500); + Serial.print("."); + counter++; + if (counter >= 60) { //30 seconds timeout - reset board + ESP.restart(); + } + } + Serial.print("Connecting to website: "); + Serial.println(host); + if (client.connect(host, 443)) { + String url = "/rele/rele1.txt"; + client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "User-Agent: ESP32\r\n" + "Connection: close\r\n\r\n"); + while (client.connected()) { + String header = client.readStringUntil('\n'); + Serial.println(header); + if (header == "\r") { + break; + } + } + String line = client.readStringUntil('\n'); + Serial.println(line); + } else { + Serial.println("Connection unsucessful"); + } + delay(5000); +} diff --git a/libraries/SSLClient/keywords.txt b/libraries/SSLClient/keywords.txt new file mode 100644 index 00000000000..62737368262 --- /dev/null +++ b/libraries/SSLClient/keywords.txt @@ -0,0 +1,36 @@ +####################################### +# Syntax Coloring Map For SSLClient +####################################### + +####################################### +# Library (KEYWORD3) +####################################### + +SSLClient KEYWORD3 + +####################################### +# Datatypes (KEYWORD1) +####################################### + +SSLClient KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +connect KEYWORD2 +write KEYWORD2 +available KEYWORD2 +config KEYWORD2 +read KEYWORD2 +flush KEYWORD2 +stop KEYWORD2 +connected KEYWORD2 +setCACert KEYWORD2 +setCertificate KEYWORD2 +setPrivateKey KEYWORD2 +setAlpnProtocols KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/libraries/SSLClient/library.properties b/libraries/SSLClient/library.properties new file mode 100644 index 00000000000..80744c4bcce --- /dev/null +++ b/libraries/SSLClient/library.properties @@ -0,0 +1,9 @@ +name=SSLClient +version=2.0.0 +author=Evandro Luis Copercini +maintainer=Github Community +sentence=Enables secure network connection (local and Internet) for the ESP32. +paragraph=With this library you can make a TLS or SSL connection to a remote server. +category=Communication +url= +architectures=esp32 diff --git a/libraries/SSLClient/src/SSLClient.cpp b/libraries/SSLClient/src/SSLClient.cpp new file mode 100644 index 00000000000..d2699d53114 --- /dev/null +++ b/libraries/SSLClient/src/SSLClient.cpp @@ -0,0 +1,341 @@ +/* + SSLClient.cpp - Client Secure class for ESP32 (based on WiFiClientSecure) + Copyright (c) 2016 Hristo Gochkov All right reserved. + Additions Copyright (C) 2017 Evandro Luis Copercini. + Conversion to Client Copyright (c) 2021 Stanislav Galabov All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "SSLClient.h" +#include + +#undef connect +#undef write +#undef read + +SSLClient::SSLClient() +{ + _connected = false; + + sslclient = new sslclient_context; + ssl_init(sslclient); + sslclient->client = nullptr; + sslclient->handshake_timeout = 120000; + _use_insecure = false; + _CA_cert = NULL; + _cert = NULL; + _private_key = NULL; + _pskIdent = NULL; + _psKey = NULL; + next = NULL; + _alpn_protos = NULL; +} + +SSLClient::SSLClient(Client& client) +{ + _connected = false; + _timeout = 0; + + sslclient = new sslclient_context; + ssl_init(sslclient); + sslclient->client = &client; + sslclient->handshake_timeout = 120000; + + _CA_cert = NULL; + _cert = NULL; + _private_key = NULL; + _pskIdent = NULL; + _psKey = NULL; + next = NULL; + _alpn_protos = NULL; +} + +SSLClient::~SSLClient() +{ + stop(); + delete sslclient; +} + +SSLClient &SSLClient::operator=(const SSLClient &other) +{ + stop(); + sslclient->client = other.sslclient->client; + _connected = other._connected; + return *this; +} + +void SSLClient::stop() +{ + if (sslclient->client != nullptr) { + _connected = false; + _peek = -1; + stop_ssl_socket(sslclient, _CA_cert, _cert, _private_key); + } +} + +int SSLClient::connect(IPAddress ip, uint16_t port) +{ + if (_pskIdent && _psKey) + return connect(ip, port, _pskIdent, _psKey); + return connect(ip, port, _CA_cert, _cert, _private_key); +} + +int SSLClient::connect(IPAddress ip, uint16_t port, int32_t timeout){ + _timeout = timeout; + return connect(ip, port); +} + +int SSLClient::connect(const char *host, uint16_t port) +{ + if (_pskIdent && _psKey) + return connect(host, port, _pskIdent, _psKey); + return connect(host, port, _CA_cert, _cert, _private_key); +} + +int SSLClient::connect(const char *host, uint16_t port, int32_t timeout){ + _timeout = timeout; + return connect(host, port); +} + +int SSLClient::connect(IPAddress ip, uint16_t port, const char *CA_cert, const char *cert, const char *private_key) +{ + return connect(ip.toString().c_str(), port, CA_cert, cert, private_key); +} + +int SSLClient::connect(const char *host, uint16_t port, const char *CA_cert, const char *cert, const char *private_key) +{ + if(_timeout > 0){ + sslclient->handshake_timeout = _timeout; + } + int ret = start_ssl_client(sslclient, host, port, _timeout, CA_cert, cert, private_key, NULL, NULL, _use_insecure, _alpn_protos); + _lastError = ret; + if (ret < 0) { + log_e("start_ssl_client: %d", ret); + stop(); + return 0; + } + _connected = true; + return 1; +} + +int SSLClient::connect(IPAddress ip, uint16_t port, const char *pskIdent, const char *psKey) { + return connect(ip.toString().c_str(), port, pskIdent, psKey); +} + +int SSLClient::connect(const char *host, uint16_t port, const char *pskIdent, const char *psKey) { + log_v("start_ssl_client with PSK"); + if(_timeout > 0){ + sslclient->handshake_timeout = _timeout; + } + int ret = start_ssl_client(sslclient, host, port, _timeout, NULL, NULL, NULL, pskIdent, psKey, _use_insecure, _alpn_protos); + _lastError = ret; + if (ret < 0) { + log_e("start_ssl_client: %d", ret); + stop(); + return 0; + } + _connected = true; + return 1; +} + +int SSLClient::peek(){ + if(_peek >= 0){ + return _peek; + } + _peek = timedRead(); + return _peek; +} + +size_t SSLClient::write(uint8_t data) +{ + return write(&data, 1); +} + +int SSLClient::read() +{ + uint8_t data = -1; + int res = read(&data, 1); + if (res < 0) { + return res; + } + return data; +} + +size_t SSLClient::write(const uint8_t *buf, size_t size) +{ + if (!_connected) { + return 0; + } + int res = send_ssl_data(sslclient, buf, size); + if (res < 0) { + stop(); + res = 0; + } + return res; +} + +int SSLClient::read(uint8_t *buf, size_t size) +{ + int peeked = 0; + int avail = available(); + if ((!buf && size) || avail <= 0) { + return -1; + } + if(!size){ + return 0; + } + if(_peek >= 0){ + buf[0] = _peek; + _peek = -1; + size--; + avail--; + if(!size || !avail){ + return 1; + } + buf++; + peeked = 1; + } + + int res = get_ssl_receive(sslclient, buf, size); + if (res < 0) { + stop(); + return peeked?peeked:res; + } + return res + peeked; +} + +int SSLClient::available() +{ + int peeked = (_peek >= 0); + if (!_connected) { + return peeked; + } + int res = data_to_read(sslclient); + if (res < 0) { + stop(); + return peeked?peeked:res; + } + return res+peeked; +} + +uint8_t SSLClient::connected() +{ + uint8_t dummy = 0; + read(&dummy, 0); + + return _connected; +} + +void SSLClient::setInsecure() +{ + _CA_cert = NULL; + _cert = NULL; + _private_key = NULL; + _pskIdent = NULL; + _psKey = NULL; + _use_insecure = true; +} + +void SSLClient::setCACert (const char *rootCA) +{ + _CA_cert = rootCA; +} + +void SSLClient::setCertificate (const char *client_ca) +{ + _cert = client_ca; +} + +void SSLClient::setPrivateKey (const char *private_key) +{ + _private_key = private_key; +} + +void SSLClient::setPreSharedKey(const char *pskIdent, const char *psKey) { + _pskIdent = pskIdent; + _psKey = psKey; +} + +bool SSLClient::verify(const char* fp, const char* domain_name) +{ + if (!sslclient) + return false; + + return verify_ssl_fingerprint(sslclient, fp, domain_name); +} + +char *SSLClient::_streamLoad(Stream& stream, size_t size) { + char *dest = (char*)malloc(size+1); + if (!dest) { + return nullptr; + } + if (size != stream.readBytes(dest, size)) { + free(dest); + dest = nullptr; + return nullptr; + } + dest[size] = '\0'; + return dest; +} + +bool SSLClient::loadCACert(Stream& stream, size_t size) { + char *dest = _streamLoad(stream, size); + bool ret = false; + if (dest) { + setCACert(dest); + ret = true; + } + return ret; +} + +bool SSLClient::loadCertificate(Stream& stream, size_t size) { + char *dest = _streamLoad(stream, size); + bool ret = false; + if (dest) { + setCertificate(dest); + ret = true; + } + return ret; +} + +bool SSLClient::loadPrivateKey(Stream& stream, size_t size) { + char *dest = _streamLoad(stream, size); + bool ret = false; + if (dest) { + setPrivateKey(dest); + ret = true; + } + return ret; +} + +int SSLClient::lastError(char *buf, const size_t size) +{ + if (!_lastError) { + return 0; + } + mbedtls_strerror(_lastError, buf, size); + return _lastError; +} + +void SSLClient::setHandshakeTimeout(unsigned long handshake_timeout) +{ + sslclient->handshake_timeout = handshake_timeout * 1000; +} + +void SSLClient::setAlpnProtocols(const char **alpn_protos) +{ + _alpn_protos = alpn_protos; +} diff --git a/libraries/SSLClient/src/SSLClient.h b/libraries/SSLClient/src/SSLClient.h new file mode 100644 index 00000000000..570878cc6f7 --- /dev/null +++ b/libraries/SSLClient/src/SSLClient.h @@ -0,0 +1,109 @@ +/* + SSLClient.h - Base class that provides Client SSL to ESP32 + (based on WiFiClientSecure) + Copyright (c) 2011 Adrian McEwen. All right reserved. + Additions Copyright (C) 2017 Evandro Luis Copercini. + Conversion to Client Copyright (c) 2021 Stanislav Galabov All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef SSLClient_h +#define SSLClient_h +#include "Arduino.h" +#include "IPAddress.h" +#include "ssl_client.h" + +class SSLClient : public Client +{ +protected: + sslclient_context *sslclient; + + int _lastError = 0; + int _peek = -1; + int _timeout = 0; + bool _use_insecure; + const char *_CA_cert; + const char *_cert; + const char *_private_key; + const char *_pskIdent; // identity for PSK cipher suites + const char *_psKey; // key in hex for PSK cipher suites + const char **_alpn_protos; + bool _connected = false; + +public: + SSLClient *next; + SSLClient(); + SSLClient(Client& client); + ~SSLClient(); + + int connect(IPAddress ip, uint16_t port); + int connect(IPAddress ip, uint16_t port, int32_t timeout); + int connect(const char *host, uint16_t port); + int connect(const char *host, uint16_t port, int32_t timeout); + int connect(IPAddress ip, uint16_t port, const char *rootCABuff, const char *cli_cert, const char *cli_key); + int connect(const char *host, uint16_t port, const char *rootCABuff, const char *cli_cert, const char *cli_key); + int connect(IPAddress ip, uint16_t port, const char *pskIdent, const char *psKey); + int connect(const char *host, uint16_t port, const char *pskIdent, const char *psKey); + int peek(); + size_t write(uint8_t data); + size_t write(const uint8_t *buf, size_t size); + int available(); + int read(); + int read(uint8_t *buf, size_t size); + void flush() {} + void stop(); + uint8_t connected(); + int lastError(char *buf, const size_t size); + void setInsecure(); // Don't validate the chain, just accept whatever is given. VERY INSECURE! + void setPreSharedKey(const char *pskIdent, const char *psKey); // psKey in Hex + void setCACert(const char *rootCA); + void setCertificate(const char *client_ca); + void setPrivateKey (const char *private_key); + bool loadCACert(Stream& stream, size_t size); + bool loadCertificate(Stream& stream, size_t size); + bool loadPrivateKey(Stream& stream, size_t size); + bool verify(const char* fingerprint, const char* domain_name); + void setHandshakeTimeout(unsigned long handshake_timeout); + void setAlpnProtocols(const char **alpn_protos); + const mbedtls_x509_crt* getPeerCertificate() { return mbedtls_ssl_get_peer_cert(&sslclient->ssl_ctx); }; + bool getFingerprintSHA256(uint8_t sha256_result[32]) { return get_peer_fingerprint(sslclient, sha256_result); }; + int setTimeout(uint32_t seconds){ return 0; } + + operator bool() + { + return connected(); + } + SSLClient &operator=(const SSLClient &other); + bool operator==(const bool value) + { + return bool() == value; + } + bool operator!=(const bool value) + { + return bool() != value; + } + bool operator==(const SSLClient &); + bool operator!=(const SSLClient &rhs) + { + return !this->operator==(rhs); + }; +private: + char *_streamLoad(Stream& stream, size_t size); + + using Print::write; +}; + +#endif /* SSLClient_h */ diff --git a/libraries/SSLClient/src/ssl_client.cpp b/libraries/SSLClient/src/ssl_client.cpp new file mode 100644 index 00000000000..b844f81c889 --- /dev/null +++ b/libraries/SSLClient/src/ssl_client.cpp @@ -0,0 +1,484 @@ +/* Provide SSL/TLS functions to ESP32 with Arduino IDE +* +* Adapted from the ssl_client1 example of mbedtls. +* +* Original Copyright (C) 2006-2015, ARM Limited, All Rights Reserved, Apache 2.0 License. +* Additions Copyright (C) 2017 Evandro Luis Copercini, Apache 2.0 License. +* Conversion to Client Copyright (c) 2021 Stanislav Galabov, Apache 2.0 License. +*/ + +#include "Arduino.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ssl_client.h" + +#if !defined(MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED) && !defined(MBEDTLS_KEY_EXCHANGE_SOME_PSK_ENABLED) +# warning "Please configure IDF framework to include mbedTLS -> Enable pre-shared-key ciphersuites and activate at least one cipher" +#else + +const char *pers = "esp32-tls"; + +static int _handle_error(int err, const char * function, int line) +{ + if(err == -30848){ + return err; + } +#ifdef MBEDTLS_ERROR_C + char error_buf[100]; + mbedtls_strerror(err, error_buf, 100); + log_e("[%s():%d]: (%d) %s", function, line, err, error_buf); +#else + log_e("[%s():%d]: code %d", function, line, err); +#endif + return err; +} + +#define handle_error(e) _handle_error(e, __FUNCTION__, __LINE__) + +static int client_recv_timeout(void *ctx, unsigned char *buf, size_t len, uint32_t timeout ) { + Client *client = reinterpret_cast(ctx); + + unsigned long target = millis() + timeout; + do { + if (client->available() < len && timeout > 0) { + delay(1); + } else { + break; + } + } while (millis() < target); + + int res = client->read(buf, len); + + return (res == 0) ? MBEDTLS_ERR_SSL_WANT_READ : res; +} + +static int client_send(void *ctx, const unsigned char *buf, size_t len ) { + Client *client = reinterpret_cast(ctx); + + return client->write(buf, len); +} + +void ssl_init(sslclient_context *ssl_client) +{ + // reset embedded pointers to zero + memset(ssl_client, 0, sizeof(sslclient_context)); + mbedtls_ssl_init(&ssl_client->ssl_ctx); + mbedtls_ssl_config_init(&ssl_client->ssl_conf); + mbedtls_ctr_drbg_init(&ssl_client->drbg_ctx); +} + + +int start_ssl_client(sslclient_context *ssl_client, const char *host, uint32_t port, int timeout, const char *rootCABuff, const char *cli_cert, const char *cli_key, const char *pskIdent, const char *psKey, bool insecure, const char **alpn_protos) +{ + char buf[512]; + int ret, flags; + int enable = 1; + log_v("Free internal heap before TLS %u", ESP.getFreeHeap()); + + if (rootCABuff == NULL && pskIdent == NULL && psKey == NULL && !insecure) { + return -1; + } + + log_v("Starting client"); + if (!ssl_client->client) { + log_e("Client not initialized"); + return -1; + } + + if (!ssl_client->client->connect(host, port)) { + log_e("Failed to connect to server"); + return -2; + } + + log_v("Seeding the random number generator"); + mbedtls_entropy_init(&ssl_client->entropy_ctx); + + ret = mbedtls_ctr_drbg_seed(&ssl_client->drbg_ctx, mbedtls_entropy_func, + &ssl_client->entropy_ctx, (const unsigned char *) pers, strlen(pers)); + if (ret < 0) { + return handle_error(ret); + } + + log_v("Setting up the SSL/TLS structure..."); + + if ((ret = mbedtls_ssl_config_defaults(&ssl_client->ssl_conf, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT)) != 0) { + return handle_error(ret); + } + + if (alpn_protos != NULL) { + log_v("Setting ALPN protocols"); + if ((ret = mbedtls_ssl_conf_alpn_protocols(&ssl_client->ssl_conf, alpn_protos) ) != 0) { + return handle_error(ret); + } + } + + // MBEDTLS_SSL_VERIFY_REQUIRED if a CA certificate is defined on Arduino IDE and + // MBEDTLS_SSL_VERIFY_NONE if not. + + if (insecure) { + mbedtls_ssl_conf_authmode(&ssl_client->ssl_conf, MBEDTLS_SSL_VERIFY_NONE); + log_i("WARNING: Skipping SSL Verification. INSECURE!"); + } else if (rootCABuff != NULL) { + log_v("Loading CA cert"); + mbedtls_x509_crt_init(&ssl_client->ca_cert); + mbedtls_ssl_conf_authmode(&ssl_client->ssl_conf, MBEDTLS_SSL_VERIFY_REQUIRED); + ret = mbedtls_x509_crt_parse(&ssl_client->ca_cert, (const unsigned char *)rootCABuff, strlen(rootCABuff) + 1); + mbedtls_ssl_conf_ca_chain(&ssl_client->ssl_conf, &ssl_client->ca_cert, NULL); + //mbedtls_ssl_conf_verify(&ssl_client->ssl_ctx, my_verify, NULL ); + if (ret < 0) { + // free the ca_cert in the case parse failed, otherwise, the old ca_cert still in the heap memory, that lead to "out of memory" crash. + mbedtls_x509_crt_free(&ssl_client->ca_cert); + return handle_error(ret); + } + } else if (pskIdent != NULL && psKey != NULL) { + log_v("Setting up PSK"); + // convert PSK from hex to binary + if ((strlen(psKey) & 1) != 0 || strlen(psKey) > 2*MBEDTLS_PSK_MAX_LEN) { + log_e("pre-shared key not valid hex or too long"); + return -1; + } + unsigned char psk[MBEDTLS_PSK_MAX_LEN]; + size_t psk_len = strlen(psKey)/2; + for (int j=0; j= '0' && c <= '9') c -= '0'; + else if (c >= 'A' && c <= 'F') c -= 'A' - 10; + else if (c >= 'a' && c <= 'f') c -= 'a' - 10; + else return -1; + psk[j/2] = c<<4; + c = psKey[j+1]; + if (c >= '0' && c <= '9') c -= '0'; + else if (c >= 'A' && c <= 'F') c -= 'A' - 10; + else if (c >= 'a' && c <= 'f') c -= 'a' - 10; + else return -1; + psk[j/2] |= c; + } + // set mbedtls config + ret = mbedtls_ssl_conf_psk(&ssl_client->ssl_conf, psk, psk_len, + (const unsigned char *)pskIdent, strlen(pskIdent)); + if (ret != 0) { + log_e("mbedtls_ssl_conf_psk returned %d", ret); + return handle_error(ret); + } + } else { + return -1; + } + + if (!insecure && cli_cert != NULL && cli_key != NULL) { + mbedtls_x509_crt_init(&ssl_client->client_cert); + mbedtls_pk_init(&ssl_client->client_key); + + log_v("Loading CRT cert"); + + ret = mbedtls_x509_crt_parse(&ssl_client->client_cert, (const unsigned char *)cli_cert, strlen(cli_cert) + 1); + if (ret < 0) { + // free the client_cert in the case parse failed, otherwise, the old client_cert still in the heap memory, that lead to "out of memory" crash. + mbedtls_x509_crt_free(&ssl_client->client_cert); + return handle_error(ret); + } + + log_v("Loading private key"); + ret = mbedtls_pk_parse_key(&ssl_client->client_key, (const unsigned char *)cli_key, strlen(cli_key) + 1, NULL, 0); + + if (ret != 0) { + mbedtls_x509_crt_free(&ssl_client->client_cert); // cert+key are free'd in pair + return handle_error(ret); + } + + mbedtls_ssl_conf_own_cert(&ssl_client->ssl_conf, &ssl_client->client_cert, &ssl_client->client_key); + } + + log_v("Setting hostname for TLS session..."); + + // Hostname set here should match CN in server certificate + if((ret = mbedtls_ssl_set_hostname(&ssl_client->ssl_ctx, host)) != 0){ + return handle_error(ret); + } + + mbedtls_ssl_conf_rng(&ssl_client->ssl_conf, mbedtls_ctr_drbg_random, &ssl_client->drbg_ctx); + + if ((ret = mbedtls_ssl_setup(&ssl_client->ssl_ctx, &ssl_client->ssl_conf)) != 0) { + return handle_error(ret); + } + + mbedtls_ssl_set_bio(&ssl_client->ssl_ctx, ssl_client->client, client_send, NULL, client_recv_timeout); + + log_v("Performing the SSL/TLS handshake..."); + unsigned long handshake_start_time=millis(); + while ((ret = mbedtls_ssl_handshake(&ssl_client->ssl_ctx)) != 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + return handle_error(ret); + } + if((millis()-handshake_start_time)>ssl_client->handshake_timeout) + return -1; + vTaskDelay(2);//2 ticks + } + + + if (cli_cert != NULL && cli_key != NULL) { + log_d("Protocol is %s Ciphersuite is %s", mbedtls_ssl_get_version(&ssl_client->ssl_ctx), mbedtls_ssl_get_ciphersuite(&ssl_client->ssl_ctx)); + if ((ret = mbedtls_ssl_get_record_expansion(&ssl_client->ssl_ctx)) >= 0) { + log_d("Record expansion is %d", ret); + } else { + log_w("Record expansion is unknown (compression)"); + } + } + + log_v("Verifying peer X.509 certificate..."); + + if ((flags = mbedtls_ssl_get_verify_result(&ssl_client->ssl_ctx)) != 0) { + memset(buf, 0, sizeof(buf)); + mbedtls_x509_crt_verify_info(buf, sizeof(buf), " ! ", flags); + log_e("Failed to verify peer certificate! verification info: %s", buf); + return handle_error(ret); + } else { + log_v("Certificate verified."); + } + + if (rootCABuff != NULL) { + mbedtls_x509_crt_free(&ssl_client->ca_cert); + } + + if (cli_cert != NULL) { + mbedtls_x509_crt_free(&ssl_client->client_cert); + } + + if (cli_key != NULL) { + mbedtls_pk_free(&ssl_client->client_key); + } + + log_v("Free internal heap after TLS %u", ESP.getFreeHeap()); + + return 1; +} + + +void stop_ssl_socket(sslclient_context *ssl_client, const char *rootCABuff, const char *cli_cert, const char *cli_key) +{ + log_v("Cleaning SSL connection."); + + ssl_client->client->stop(); + + // avoid memory leak if ssl connection attempt failed + if (ssl_client->ssl_conf.ca_chain != NULL) { + mbedtls_x509_crt_free(&ssl_client->ca_cert); + } + if (ssl_client->ssl_conf.key_cert != NULL) { + mbedtls_x509_crt_free(&ssl_client->client_cert); + mbedtls_pk_free(&ssl_client->client_key); + } + mbedtls_ssl_free(&ssl_client->ssl_ctx); + mbedtls_ssl_config_free(&ssl_client->ssl_conf); + mbedtls_ctr_drbg_free(&ssl_client->drbg_ctx); + mbedtls_entropy_free(&ssl_client->entropy_ctx); + // reset embedded pointers to zero + memset(ssl_client, 0, sizeof(sslclient_context)); + ssl_client->client = nullptr; +} + + +int data_to_read(sslclient_context *ssl_client) +{ + int ret, res; + ret = mbedtls_ssl_read(&ssl_client->ssl_ctx, NULL, 0); + //log_e("RET: %i",ret); //for low level debug + res = mbedtls_ssl_get_bytes_avail(&ssl_client->ssl_ctx); + //log_e("RES: %i",res); //for low level debug + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret < 0) { + return handle_error(ret); + } + + return res; +} + +int send_ssl_data(sslclient_context *ssl_client, const uint8_t *data, size_t len) +{ + log_v("Writing HTTP request with %d bytes...", len); //for low level debug + int ret = -1; + + while ((ret = mbedtls_ssl_write(&ssl_client->ssl_ctx, data, len)) <= 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret < 0) { + log_v("Handling error %d", ret); //for low level debug + return handle_error(ret); + } + //wait for space to become available + vTaskDelay(2); + } + + return ret; +} + +int get_ssl_receive(sslclient_context *ssl_client, uint8_t *data, int length) +{ + //log_d( "Reading HTTP response..."); //for low level debug + int ret = -1; + + ret = mbedtls_ssl_read(&ssl_client->ssl_ctx, data, length); + + //log_v( "%d bytes read", ret); //for low level debug + return ret; +} + +static bool parseHexNibble(char pb, uint8_t* res) +{ + if (pb >= '0' && pb <= '9') { + *res = (uint8_t) (pb - '0'); return true; + } else if (pb >= 'a' && pb <= 'f') { + *res = (uint8_t) (pb - 'a' + 10); return true; + } else if (pb >= 'A' && pb <= 'F') { + *res = (uint8_t) (pb - 'A' + 10); return true; + } + return false; +} + +// Compare a name from certificate and domain name, return true if they match +static bool matchName(const std::string& name, const std::string& domainName) +{ + size_t wildcardPos = name.find('*'); + if (wildcardPos == std::string::npos) { + // Not a wildcard, expect an exact match + return name == domainName; + } + + size_t firstDotPos = name.find('.'); + if (wildcardPos > firstDotPos) { + // Wildcard is not part of leftmost component of domain name + // Do not attempt to match (rfc6125 6.4.3.1) + return false; + } + if (wildcardPos != 0 || firstDotPos != 1) { + // Matching of wildcards such as baz*.example.com and b*z.example.com + // is optional. Maybe implement this in the future? + return false; + } + size_t domainNameFirstDotPos = domainName.find('.'); + if (domainNameFirstDotPos == std::string::npos) { + return false; + } + return domainName.substr(domainNameFirstDotPos) == name.substr(firstDotPos); +} + +// Verifies certificate provided by the peer to match specified SHA256 fingerprint +bool verify_ssl_fingerprint(sslclient_context *ssl_client, const char* fp, const char* domain_name) +{ + // Convert hex string to byte array + uint8_t fingerprint_local[32]; + int len = strlen(fp); + int pos = 0; + for (size_t i = 0; i < sizeof(fingerprint_local); ++i) { + while (pos < len && ((fp[pos] == ' ') || (fp[pos] == ':'))) { + ++pos; + } + if (pos > len - 2) { + log_d("pos:%d len:%d fingerprint too short", pos, len); + return false; + } + uint8_t high, low; + if (!parseHexNibble(fp[pos], &high) || !parseHexNibble(fp[pos+1], &low)) { + log_d("pos:%d len:%d invalid hex sequence: %c%c", pos, len, fp[pos], fp[pos+1]); + return false; + } + pos += 2; + fingerprint_local[i] = low | (high << 4); + } + + // Calculate certificate's SHA256 fingerprint + uint8_t fingerprint_remote[32]; + if(!get_peer_fingerprint(ssl_client, fingerprint_remote)) + return false; + + // Check if fingerprints match + if (memcmp(fingerprint_local, fingerprint_remote, 32)) + { + log_d("fingerprint doesn't match"); + return false; + } + + // Additionally check if certificate has domain name if provided + if (domain_name) + return verify_ssl_dn(ssl_client, domain_name); + else + return true; +} + +bool get_peer_fingerprint(sslclient_context *ssl_client, uint8_t sha256[32]) +{ + if (!ssl_client) { + log_d("Invalid ssl_client pointer"); + return false; + }; + + const mbedtls_x509_crt* crt = mbedtls_ssl_get_peer_cert(&ssl_client->ssl_ctx); + if (!crt) { + log_d("Failed to get peer cert."); + return false; + }; + + mbedtls_sha256_context sha256_ctx; + mbedtls_sha256_init(&sha256_ctx); + mbedtls_sha256_starts(&sha256_ctx, false); + mbedtls_sha256_update(&sha256_ctx, crt->raw.p, crt->raw.len); + mbedtls_sha256_finish(&sha256_ctx, sha256); + + return true; +} + +// Checks if peer certificate has specified domain in CN or SANs +bool verify_ssl_dn(sslclient_context *ssl_client, const char* domain_name) +{ + log_d("domain name: '%s'", (domain_name)?domain_name:"(null)"); + std::string domain_name_str(domain_name); + std::transform(domain_name_str.begin(), domain_name_str.end(), domain_name_str.begin(), ::tolower); + + // Get certificate provided by the peer + const mbedtls_x509_crt* crt = mbedtls_ssl_get_peer_cert(&ssl_client->ssl_ctx); + + // Check for domain name in SANs + const mbedtls_x509_sequence* san = &crt->subject_alt_names; + while (san != nullptr) + { + std::string san_str((const char*)san->buf.p, san->buf.len); + std::transform(san_str.begin(), san_str.end(), san_str.begin(), ::tolower); + + if (matchName(san_str, domain_name_str)) + return true; + + log_d("SAN '%s': no match", san_str.c_str()); + + // Fetch next SAN + san = san->next; + } + + // Check for domain name in CN + const mbedtls_asn1_named_data* common_name = &crt->subject; + while (common_name != nullptr) + { + // While iterating through DN objects, check for CN object + if (!MBEDTLS_OID_CMP(MBEDTLS_OID_AT_CN, &common_name->oid)) + { + std::string common_name_str((const char*)common_name->val.p, common_name->val.len); + + if (matchName(common_name_str, domain_name_str)) + return true; + + log_d("CN '%s': not match", common_name_str.c_str()); + } + + // Fetch next DN object + common_name = common_name->next; + } + + return false; +} +#endif + diff --git a/libraries/SSLClient/src/ssl_client.h b/libraries/SSLClient/src/ssl_client.h new file mode 100644 index 00000000000..5ac849a4b0b --- /dev/null +++ b/libraries/SSLClient/src/ssl_client.h @@ -0,0 +1,42 @@ +/* Provide SSL/TLS functions to ESP32 with Arduino IDE + * by Evandro Copercini - 2017 - Apache 2.0 License + */ + +#ifndef ARD_SSL_H +#define ARD_SSL_H +#include "mbedtls/platform.h" +#include "mbedtls/net.h" +#include "mbedtls/debug.h" +#include "mbedtls/ssl.h" +#include "mbedtls/entropy.h" +#include "mbedtls/ctr_drbg.h" +#include "mbedtls/error.h" + +#include + +typedef struct sslclient_context { + Client *client; + mbedtls_ssl_context ssl_ctx; + mbedtls_ssl_config ssl_conf; + + mbedtls_ctr_drbg_context drbg_ctx; + mbedtls_entropy_context entropy_ctx; + + mbedtls_x509_crt ca_cert; + mbedtls_x509_crt client_cert; + mbedtls_pk_context client_key; + + unsigned long handshake_timeout; +} sslclient_context; + + +void ssl_init(sslclient_context *ssl_client); +int start_ssl_client(sslclient_context *ssl_client, const char *host, uint32_t port, int timeout, const char *rootCABuff, const char *cli_cert, const char *cli_key, const char *pskIdent, const char *psKey, bool insecure, const char **alpn_protos); +void stop_ssl_socket(sslclient_context *ssl_client, const char *rootCABuff, const char *cli_cert, const char *cli_key); +int data_to_read(sslclient_context *ssl_client); +int send_ssl_data(sslclient_context *ssl_client, const uint8_t *data, size_t len); +int get_ssl_receive(sslclient_context *ssl_client, uint8_t *data, int length); +bool verify_ssl_fingerprint(sslclient_context *ssl_client, const char* fp, const char* domain_name); +bool verify_ssl_dn(sslclient_context *ssl_client, const char* domain_name); +bool get_peer_fingerprint(sslclient_context *ssl_client, uint8_t sha256[32]); +#endif pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy