diff --git a/CMakeLists.txt b/CMakeLists.txt index 59d1ded8e80..c715431676c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,6 +100,8 @@ set(LIBRARY_SRCS libraries/SimpleBLE/src/SimpleBLE.cpp libraries/SPIFFS/src/SPIFFS.cpp libraries/SPI/src/SPI.cpp + libraries/SSLClient/src/ssl_client.cpp + libraries/SSLClient/src/SSLClient.cpp libraries/Ticker/src/Ticker.cpp libraries/Update/src/Updater.cpp libraries/Update/src/HttpsOTAUpdate.cpp @@ -186,6 +188,7 @@ set(includedirs libraries/SimpleBLE/src libraries/SPIFFS/src libraries/SPI/src + libraries/SSLClient/src libraries/Ticker/src libraries/Update/src libraries/USB/src 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/examples/WiFiClientShowPeerCredentials/WiFiClientShowPeerCredentials.ino b/libraries/SSLClient/examples/WiFiClientShowPeerCredentials/WiFiClientShowPeerCredentials.ino new file mode 100644 index 00000000000..96bb5fb907e --- /dev/null +++ b/libraries/SSLClient/examples/WiFiClientShowPeerCredentials/WiFiClientShowPeerCredentials.ino @@ -0,0 +1,98 @@ +// WiFiClientShowPeerCredentials +// +// Example of a establishing a secure connection and then +// showing the fingerprint of the certificate. This can +// be useful in an IoT setting to know for sure that you +// are connecting to the right server. Especally in +// situations where you cannot hardcode a trusted root +// certificate for long periods of time (as they tend to +// get replaced more often than the lifecycle of IoT +// hardware). +// + +#include +#include +#include + +#ifndef WIFI_NETWORK +#define WIFI_NETWORK "MyWifiNetwork" +#endif + +#ifndef WIFI_PASSWD +#define WIFI_PASSWD "MySecretWifiPassword" +#endif + +#define URL "https://arduino.cc" + +void demo() { + WiFiClient wificlient; + SSLClient client(wificlient); + client.setInsecure(); // + + HTTPClient https; + if (!https.begin(client, URL )) { + Serial.println("HTTPS setup failed"); + return; + }; + + https.setTimeout(5000); + + int httpCode = https.GET(); + if (httpCode != 200) { + Serial.print("Connect failed: "); + Serial.println(https.errorToString(httpCode)); + return; + } + + const mbedtls_x509_crt* peer = client.getPeerCertificate(); + + // Show general output / certificate information + // + char buf[1024]; + int l = mbedtls_x509_crt_info (buf, sizeof(buf), "", peer); + if (l <= 0) { + Serial.println("Peer conversion to printable buffer failed"); + return; + }; + Serial.println(); + Serial.println(buf); + + uint8_t fingerprint_remote[32]; + if (!client.getFingerprintSHA256(fingerprint_remote)) { + Serial.println("Failed to get the fingerprint"); + return; + } + // Fingerprint late 2021 + Serial.println("Expecting Fingerprint (SHA256): 70 CF A4 B7 5D 09 E9 2A 52 A8 B6 85 B5 0B D6 BE 83 47 83 5B 3A 4D 3C 3E 32 30 EC 1D 61 98 D7 0F"); + Serial.print( " Received Fingerprint (SHA256): "); + + for (int i = 0; i < 32; i++) { + Serial.print(fingerprint_remote[i], HEX); + Serial.print(" "); + }; + Serial.println(""); +}; + +void setup() { + Serial.begin(115200); + Serial.println("Started " __FILE__ " build " __DATE__ " " __TIME__); + + WiFi.mode(WIFI_STA); + WiFi.begin(WIFI_NETWORK, WIFI_PASSWD); + + while (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.println("Wifi fail - rebooting"); + delay(5000); + ESP.restart(); + } +} + +void loop() { + bool already_tried = false; + if ((millis() < 1000) || already_tried) + return; + already_tried = true; + + // Run the test just once. + demo(); +} 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..4b860e5d6ce --- /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" + +#ifndef 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