Unverified Commit 13fac087 authored by Dirk-Willem van Gulik's avatar Dirk-Willem van Gulik Committed by GitHub

feature: create a Trust on First Use example (#9103)

* feature: create a Trust on First Use example the quell the increasingly common copy & paste of the insecure approach making it to production

* Quell CI/CD runs on non-WiFi supporting hardare

* Update libraries/WiFiClientSecure/examples/WiFiClientInsecure/WiFiClientInsecure.ino

typo/improvement to text
Co-authored-by: default avatarLucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com>

* Update libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/WiFiClientTrustOnFirstUse.ino

typo/improvement to text
Co-authored-by: default avatarLucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com>

* Update libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/WiFiClientTrustOnFirstUse.ino

Fix formatting
Co-authored-by: default avatarLucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com>

* Update libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/WiFiClientTrustOnFirstUse.ino

typo/improvement to text
Co-authored-by: default avatarLucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com>

* Update libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/WiFiClientTrustOnFirstUse.ino

typo/improvement to text
Co-authored-by: default avatarLucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com>

* Update libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/WiFiClientTrustOnFirstUse.ino

typo/improvement to text
Co-authored-by: default avatarLucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com>

* Update libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/WiFiClientTrustOnFirstUse.ino

typo/improvement to text
Co-authored-by: default avatarLucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com>

* Update libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/WiFiClientTrustOnFirstUse.ino

typo/improvement to text
Co-authored-by: default avatarLucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com>

* Various things can all stop_ssl_socket() which sets the socket to -1; but the WiFiClientSecure checks for _connected. So we want to make sure the latter is always set. And thus have moved the state handling around *ssl_client down into the C code; below WiFiClientSecure.

* Unitialized NVRAM/EEPROM is actual set to 0xFF; so adjust for this. And print the LF/CR for the header lines.

* Update libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/WiFiClientTrustOnFirstUse.ino

---------
Co-authored-by: default avatarMe No Dev <me-no-dev@users.noreply.github.com>
Co-authored-by: default avatarLucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com>
parent 58bea5c1
#include <WiFiClientSecure.h>
/* This is a very INSECURE approach.
* If for some reason the secure, proper example WiFiClientSecure
* does not work for you; then you may want to check the
* WiFiClientTrustOnFirstUse example first. It is less secure than
* WiFiClientSecure, but a lot better than this totally insecure
* approach shown below.
*/
const char* ssid = "your-ssid"; // your network SSID (name of wifi network)
const char* password = "your-password"; // your network password
......
/* For any secure connection - it is (at least) essential for the
the client to verify that it is talking with the server it
thinks it is talking to. And not some (invisible) man in the middle.
See https://en.wikipedia.org/wiki/Man-in-the-middle_attack,
https://www.ai.rug.nl/mas/finishedprojects/2011/TLS/hermsencomputerservices.nl/mas/mitm.html or
https://medium.com/@munteanu210/ssl-certificates-vs-man-in-the-middle-attacks-3fb7846fa5db
for some background on this.
Unfortunatley this means that one needs to hardcode a server
public key, certificate or some cryptographically strong hash
thereoff into the code, to verify that you are indeed talking to
the right server. This is sometimes somewhat impractical. Especially
if you do not know the server in advance; or if your code needs to be
stable ovr very long times - during which the server may change.
However completely dispensing with any checks (See the WifiClientInSecure
example) is also not a good idea either.
This example gives you some middle ground; "Trust on First Use" --
TOFU - see https://developer.mozilla.org/en-US/docs/Glossary/TOFU or
https://en.wikipedia.org/wiki/Trust_on_first_use).
In this scheme; we start the very first time without any security checks
but once we have our first connection; we store the public crytpographic
details (or a proxy, such as a sha256 of this). And then we use this for
any subsequent connections.
The assumption here is that we do our very first connection in a somewhat
trusted network environment; where the chance of a man in the middle is
very low; or one where the person doing the first run can check the
details manually.
So this is not quite as good as building a CA certificate into your
code (as per the WifiClientSecure example). But not as bad as something
with no trust management at all.
To make it possible for the enduser to 'reset' this trust; the
startup sequence checks if a certain GPIO is low (assumed to be wired
to some physical button or jumper on the PCB). And we only allow
the TOFU to be configured when this pin is LOW.
*/
#ifndef WIFI_NETWORK
#define WIFI_NETWORK "Your Wifi SSID"
#endif
#ifndef WIFI_PASSWD
#define WIFI_PASSWD "your-secret-wifi-password"
#endif
const char* ssid = WIFI_NETWORK; // your network SSID (name of wifi network)
const char* password = WIFI_PASSWD; // your network password
const char* server = "www.howsmyssl.com"; // Server to test with.
const int TOFU_RESET_BUTTON = 35; /* Trust reset button wired between GPIO 35 and GND (pulldown) */
#include <WiFiClientSecure.h>
#include <EEPROM.h>
/* Set aside some persistant memory (i.e. memory that is preserved on reboots and
power cycling; and will generally survive software updates as well.
*/
EEPROMClass TOFU("tofu0");
// Utility function; checks if a given buffer is entirly
// with with 0 bytes over its full length. Returns 0 on
// succes; a non zero value on fail.
//
static int memcmpzero(unsigned char * ptr, size_t len) {
while (len--) if (0xff != *ptr++) return -1;
return 0;
};
static void printSHA256(unsigned char * ptr) {
for (int i = 0; i < 32; i++) Serial.printf("%s%02x", i ? ":" : "", ptr[i]);
Serial.println("");
};
WiFiClientSecure client;
bool get_tofu();
bool doTOFU_Protected_Connection(uint8_t * fingerprint_tofu);
void setup() {
bool tofu_reset = false;
//Initialize serial and wait for port to open:
Serial.begin(115200);
delay(100);
if (!TOFU.begin(32)) {
Serial.println("Could not initialsize the EEPROM");
return;
}
uint8_t fingerprint_tofu[32];
// reset the trust if the tofu reset button is pressed.
//
pinMode(TOFU_RESET_BUTTON, INPUT_PULLUP);
if (digitalRead(TOFU_RESET_BUTTON) == LOW) {
Serial.println("The TOFU reset button is pressed.");
tofu_reset = true;
}
/* if the button is not pressed; see if we can get the TOFU
fingerprint from the EEPROM.
*/
else if (32 != TOFU.readBytes(0, fingerprint_tofu, 32)) {
Serial.println("Failed to get the fingerprint from memory.");
tofu_reset = true;
}
/* And check that the EEPROM value is not all 0's; in which
case we also need to do a TOFU.
*/
else if (!memcmpzero(fingerprint_tofu, 32)) {
Serial.println("TOFU fingerprint in memory all zero.");
tofu_reset = true;
};
if (!tofu_reset) {
Serial.print("TOFU pegged to fingerprint: SHA256=");
printSHA256(fingerprint_tofu);
Serial.print("Note: You can check this fingerprint by going to the URL\n"
"<https://");
Serial.print(server);
Serial.println("> and then click on the lock icon.\n");
};
// attempt to connect to Wifi network:
Serial.print("Attempting to connect to SSID: ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
// wait 1 second for re-trying
delay(1000);
}
Serial.print("Connected to ");
Serial.println(ssid);
if (tofu_reset) {
Serial.println("Resetting trust fingerprint.");
if (!get_tofu()) {
Serial.println("Trust reset failed. Giving up");
return;
}
Serial.println("(New) Trust of First used configured. Rebooting in 3 seconds");
delay(3 * 1000);
ESP.restart();
};
Serial.println("Trying to connect to a server; using TOFU details from the eeprom");
if (doTOFU_Protected_Connection(fingerprint_tofu))
Serial.println("ALL OK");
}
bool get_tofu() {
Serial.println("\nStarting our insecure connection to server...");
client.setInsecure();//skip verification
if (!client.connect(server, 443)) {
Serial.println("Connection failed!");
client.stop();
return false;
};
Serial.println("Connected to server. Extracting trust data.");
// Now extract the data of the certificate and show it to
// the user over the serial connection for optional
// verification.
const mbedtls_x509_crt* peer = client.getPeerCertificate();
char buf[1024];
int l = mbedtls_x509_crt_info(buf, sizeof(buf), "", peer);
if (l <= 0) {
Serial.println("Peer conversion to printable buffer failed");
client.stop();
return false;
};
Serial.println();
Serial.println(buf);
// Extract the fingerprint - and store this in our EEPROM
// to be used for future validation.
uint8_t fingerprint_remote[32];
if (!client.getFingerprintSHA256(fingerprint_remote)) {
Serial.println("Failed to get the fingerprint");
client.stop();
return false;
}
if (
(32 != TOFU.writeBytes(0, fingerprint_remote, 32)) ||
(!TOFU.commit())
) {
Serial.println("Could not write the fingerprint to the EEPROM");
client.stop();
return false;
};
TOFU.end();
client.stop();
Serial.print("TOFU pegged to fingerprint: SHA256=");
printSHA256(fingerprint_remote);
return true;
};
bool doTOFU_Protected_Connection(uint8_t * fingerprint_tofu) {
// As we're not using a (CA) certificate to check the
// connection; but the hash of the peer - we need to initially
// allow the connection to be set up without the CA check.
client.setInsecure();//skip verification
if (!client.connect(server, 443)) {
Serial.println("Connection failed!");
client.stop();
return false;
};
// Now that we're connected - we can check that we have
// end to end trust - by comparing the fingerprint we (now)
// see (of the server certificate) to the one we have stored
// in our EEPROM as part of an earlier trust-on-first use.
uint8_t fingerprint_remote[32];
if (!client.getFingerprintSHA256(fingerprint_remote)) {
Serial.println("Failed to get the fingerprint of the server");
client.stop();
return false;
}
if (memcmp(fingerprint_remote, fingerprint_tofu, 32)) {
Serial.println("TOFU fingerprint not the same as the one from the server.");
Serial.print("TOFU : SHA256=");
printSHA256(fingerprint_tofu);
Serial.print("Remote: SHA256=");
printSHA256(fingerprint_remote);
Serial.println(" : NOT identical -- Aborting!");
client.stop();
return false;
};
Serial.println("All well - you are talking to the same server as\n"
"when you set up TOFU. So we can now do a GET.\n\n");
client.println("GET /a/check HTTP/1.0");
client.print("Host: " ); client.println(server);
client.println("Connection: close");
client.println();
bool inhdr = true;
while (client.connected()) {
String line = client.readStringUntil('\n');
Serial.println(line);
if (inhdr && line == "\r") {
inhdr = false;
Serial.println("-- headers received. Payload follows\n\n");
}
}
Serial.println("\n\n-- Payload ended.");
client.stop();
return true;
}
void loop() {}
......@@ -91,15 +91,12 @@ WiFiClientSecure &WiFiClientSecure::operator=(const WiFiClientSecure &other)
void WiFiClientSecure::stop()
{
if (sslclient->socket >= 0) {
close(sslclient->socket);
sslclient->socket = -1;
_connected = false;
_peek = -1;
_lastReadTimeout = 0;
_lastWriteTimeout = 0;
}
stop_ssl_socket(sslclient, _CA_cert, _cert, _private_key);
_connected = false;
_peek = -1;
_lastReadTimeout = 0;
_lastWriteTimeout = 0;
}
int WiFiClientSecure::connect(IPAddress ip, uint16_t port)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment