Skip to content

Commit 13fac08

Browse files
dirkxme-no-devlucasssvaz
authored
feature: create a Trust on First Use example (espressif#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: Lucas Saavedra Vaz <[email protected]> * Update libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/WiFiClientTrustOnFirstUse.ino typo/improvement to text Co-authored-by: Lucas Saavedra Vaz <[email protected]> * Update libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/WiFiClientTrustOnFirstUse.ino Fix formatting Co-authored-by: Lucas Saavedra Vaz <[email protected]> * Update libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/WiFiClientTrustOnFirstUse.ino typo/improvement to text Co-authored-by: Lucas Saavedra Vaz <[email protected]> * Update libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/WiFiClientTrustOnFirstUse.ino typo/improvement to text Co-authored-by: Lucas Saavedra Vaz <[email protected]> * Update libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/WiFiClientTrustOnFirstUse.ino typo/improvement to text Co-authored-by: Lucas Saavedra Vaz <[email protected]> * Update libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/WiFiClientTrustOnFirstUse.ino typo/improvement to text Co-authored-by: Lucas Saavedra Vaz <[email protected]> * Update libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/WiFiClientTrustOnFirstUse.ino typo/improvement to text Co-authored-by: Lucas Saavedra Vaz <[email protected]> * 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: Me No Dev <[email protected]> Co-authored-by: Lucas Saavedra Vaz <[email protected]>
1 parent 58bea5c commit 13fac08

File tree

4 files changed

+277
-8
lines changed

4 files changed

+277
-8
lines changed

libraries/WiFiClientSecure/examples/WiFiClientInsecure/WiFiClientInsecure.ino

+8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
#include <WiFiClientSecure.h>
22

3+
/* This is a very INSECURE approach.
4+
* If for some reason the secure, proper example WiFiClientSecure
5+
* does not work for you; then you may want to check the
6+
* WiFiClientTrustOnFirstUse example first. It is less secure than
7+
* WiFiClientSecure, but a lot better than this totally insecure
8+
* approach shown below.
9+
*/
10+
311
const char* ssid = "your-ssid"; // your network SSID (name of wifi network)
412
const char* password = "your-password"; // your network password
513

libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/.skip.esp32h2

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
/* For any secure connection - it is (at least) essential for the
2+
the client to verify that it is talking with the server it
3+
thinks it is talking to. And not some (invisible) man in the middle.
4+
5+
See https://en.wikipedia.org/wiki/Man-in-the-middle_attack,
6+
https://www.ai.rug.nl/mas/finishedprojects/2011/TLS/hermsencomputerservices.nl/mas/mitm.html or
7+
https://medium.com/@munteanu210/ssl-certificates-vs-man-in-the-middle-attacks-3fb7846fa5db
8+
for some background on this.
9+
10+
Unfortunatley this means that one needs to hardcode a server
11+
public key, certificate or some cryptographically strong hash
12+
thereoff into the code, to verify that you are indeed talking to
13+
the right server. This is sometimes somewhat impractical. Especially
14+
if you do not know the server in advance; or if your code needs to be
15+
stable ovr very long times - during which the server may change.
16+
17+
However completely dispensing with any checks (See the WifiClientInSecure
18+
example) is also not a good idea either.
19+
20+
This example gives you some middle ground; "Trust on First Use" --
21+
TOFU - see https://developer.mozilla.org/en-US/docs/Glossary/TOFU or
22+
https://en.wikipedia.org/wiki/Trust_on_first_use).
23+
24+
In this scheme; we start the very first time without any security checks
25+
but once we have our first connection; we store the public crytpographic
26+
details (or a proxy, such as a sha256 of this). And then we use this for
27+
any subsequent connections.
28+
29+
The assumption here is that we do our very first connection in a somewhat
30+
trusted network environment; where the chance of a man in the middle is
31+
very low; or one where the person doing the first run can check the
32+
details manually.
33+
34+
So this is not quite as good as building a CA certificate into your
35+
code (as per the WifiClientSecure example). But not as bad as something
36+
with no trust management at all.
37+
38+
To make it possible for the enduser to 'reset' this trust; the
39+
startup sequence checks if a certain GPIO is low (assumed to be wired
40+
to some physical button or jumper on the PCB). And we only allow
41+
the TOFU to be configured when this pin is LOW.
42+
*/
43+
#ifndef WIFI_NETWORK
44+
#define WIFI_NETWORK "Your Wifi SSID"
45+
#endif
46+
47+
#ifndef WIFI_PASSWD
48+
#define WIFI_PASSWD "your-secret-wifi-password"
49+
#endif
50+
51+
const char* ssid = WIFI_NETWORK; // your network SSID (name of wifi network)
52+
const char* password = WIFI_PASSWD; // your network password
53+
const char* server = "www.howsmyssl.com"; // Server to test with.
54+
55+
const int TOFU_RESET_BUTTON = 35; /* Trust reset button wired between GPIO 35 and GND (pulldown) */
56+
57+
#include <WiFiClientSecure.h>
58+
#include <EEPROM.h>
59+
60+
/* Set aside some persistant memory (i.e. memory that is preserved on reboots and
61+
power cycling; and will generally survive software updates as well.
62+
*/
63+
EEPROMClass TOFU("tofu0");
64+
65+
// Utility function; checks if a given buffer is entirly
66+
// with with 0 bytes over its full length. Returns 0 on
67+
// succes; a non zero value on fail.
68+
//
69+
static int memcmpzero(unsigned char * ptr, size_t len) {
70+
while (len--) if (0xff != *ptr++) return -1;
71+
return 0;
72+
};
73+
74+
static void printSHA256(unsigned char * ptr) {
75+
for (int i = 0; i < 32; i++) Serial.printf("%s%02x", i ? ":" : "", ptr[i]);
76+
Serial.println("");
77+
};
78+
79+
WiFiClientSecure client;
80+
81+
bool get_tofu();
82+
bool doTOFU_Protected_Connection(uint8_t * fingerprint_tofu);
83+
84+
void setup() {
85+
bool tofu_reset = false;
86+
//Initialize serial and wait for port to open:
87+
Serial.begin(115200);
88+
delay(100);
89+
90+
if (!TOFU.begin(32)) {
91+
Serial.println("Could not initialsize the EEPROM");
92+
return;
93+
}
94+
uint8_t fingerprint_tofu[32];
95+
96+
// reset the trust if the tofu reset button is pressed.
97+
//
98+
pinMode(TOFU_RESET_BUTTON, INPUT_PULLUP);
99+
if (digitalRead(TOFU_RESET_BUTTON) == LOW) {
100+
Serial.println("The TOFU reset button is pressed.");
101+
tofu_reset = true;
102+
}
103+
/* if the button is not pressed; see if we can get the TOFU
104+
fingerprint from the EEPROM.
105+
*/
106+
else if (32 != TOFU.readBytes(0, fingerprint_tofu, 32)) {
107+
Serial.println("Failed to get the fingerprint from memory.");
108+
tofu_reset = true;
109+
}
110+
/* And check that the EEPROM value is not all 0's; in which
111+
case we also need to do a TOFU.
112+
*/
113+
else if (!memcmpzero(fingerprint_tofu, 32)) {
114+
Serial.println("TOFU fingerprint in memory all zero.");
115+
tofu_reset = true;
116+
};
117+
if (!tofu_reset) {
118+
Serial.print("TOFU pegged to fingerprint: SHA256=");
119+
printSHA256(fingerprint_tofu);
120+
Serial.print("Note: You can check this fingerprint by going to the URL\n"
121+
"<https://");
122+
Serial.print(server);
123+
Serial.println("> and then click on the lock icon.\n");
124+
};
125+
126+
// attempt to connect to Wifi network:
127+
Serial.print("Attempting to connect to SSID: ");
128+
Serial.println(ssid);
129+
WiFi.begin(ssid, password);
130+
while (WiFi.status() != WL_CONNECTED) {
131+
Serial.print(".");
132+
// wait 1 second for re-trying
133+
delay(1000);
134+
}
135+
136+
Serial.print("Connected to ");
137+
Serial.println(ssid);
138+
139+
if (tofu_reset) {
140+
Serial.println("Resetting trust fingerprint.");
141+
if (!get_tofu()) {
142+
Serial.println("Trust reset failed. Giving up");
143+
return;
144+
}
145+
Serial.println("(New) Trust of First used configured. Rebooting in 3 seconds");
146+
delay(3 * 1000);
147+
ESP.restart();
148+
};
149+
150+
Serial.println("Trying to connect to a server; using TOFU details from the eeprom");
151+
152+
if (doTOFU_Protected_Connection(fingerprint_tofu))
153+
Serial.println("ALL OK");
154+
}
155+
156+
bool get_tofu() {
157+
Serial.println("\nStarting our insecure connection to server...");
158+
client.setInsecure();//skip verification
159+
160+
if (!client.connect(server, 443)) {
161+
Serial.println("Connection failed!");
162+
client.stop();
163+
return false;
164+
};
165+
166+
Serial.println("Connected to server. Extracting trust data.");
167+
168+
// Now extract the data of the certificate and show it to
169+
// the user over the serial connection for optional
170+
// verification.
171+
const mbedtls_x509_crt* peer = client.getPeerCertificate();
172+
char buf[1024];
173+
int l = mbedtls_x509_crt_info(buf, sizeof(buf), "", peer);
174+
if (l <= 0) {
175+
Serial.println("Peer conversion to printable buffer failed");
176+
client.stop();
177+
return false;
178+
};
179+
Serial.println();
180+
Serial.println(buf);
181+
182+
// Extract the fingerprint - and store this in our EEPROM
183+
// to be used for future validation.
184+
185+
uint8_t fingerprint_remote[32];
186+
if (!client.getFingerprintSHA256(fingerprint_remote)) {
187+
Serial.println("Failed to get the fingerprint");
188+
client.stop();
189+
return false;
190+
}
191+
if (
192+
(32 != TOFU.writeBytes(0, fingerprint_remote, 32)) ||
193+
(!TOFU.commit())
194+
) {
195+
Serial.println("Could not write the fingerprint to the EEPROM");
196+
client.stop();
197+
return false;
198+
};
199+
TOFU.end();
200+
client.stop();
201+
202+
Serial.print("TOFU pegged to fingerprint: SHA256=");
203+
printSHA256(fingerprint_remote);
204+
205+
return true;
206+
};
207+
208+
bool doTOFU_Protected_Connection(uint8_t * fingerprint_tofu) {
209+
210+
// As we're not using a (CA) certificate to check the
211+
// connection; but the hash of the peer - we need to initially
212+
// allow the connection to be set up without the CA check.
213+
client.setInsecure();//skip verification
214+
215+
if (!client.connect(server, 443)) {
216+
Serial.println("Connection failed!");
217+
client.stop();
218+
return false;
219+
};
220+
221+
// Now that we're connected - we can check that we have
222+
// end to end trust - by comparing the fingerprint we (now)
223+
// see (of the server certificate) to the one we have stored
224+
// in our EEPROM as part of an earlier trust-on-first use.
225+
uint8_t fingerprint_remote[32];
226+
if (!client.getFingerprintSHA256(fingerprint_remote)) {
227+
Serial.println("Failed to get the fingerprint of the server");
228+
client.stop();
229+
return false;
230+
}
231+
if (memcmp(fingerprint_remote, fingerprint_tofu, 32)) {
232+
Serial.println("TOFU fingerprint not the same as the one from the server.");
233+
Serial.print("TOFU : SHA256=");
234+
printSHA256(fingerprint_tofu);
235+
Serial.print("Remote: SHA256=");
236+
printSHA256(fingerprint_remote);
237+
Serial.println(" : NOT identical -- Aborting!");
238+
client.stop();
239+
return false;
240+
};
241+
242+
Serial.println("All well - you are talking to the same server as\n"
243+
"when you set up TOFU. So we can now do a GET.\n\n");
244+
245+
client.println("GET /a/check HTTP/1.0");
246+
client.print("Host: " ); client.println(server);
247+
client.println("Connection: close");
248+
client.println();
249+
250+
bool inhdr = true;
251+
while (client.connected()) {
252+
String line = client.readStringUntil('\n');
253+
Serial.println(line);
254+
if (inhdr && line == "\r") {
255+
inhdr = false;
256+
Serial.println("-- headers received. Payload follows\n\n");
257+
}
258+
}
259+
Serial.println("\n\n-- Payload ended.");
260+
client.stop();
261+
return true;
262+
}
263+
264+
void loop() {}

libraries/WiFiClientSecure/src/WiFiClientSecure.cpp

+5-8
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,12 @@ WiFiClientSecure &WiFiClientSecure::operator=(const WiFiClientSecure &other)
9191

9292
void WiFiClientSecure::stop()
9393
{
94-
if (sslclient->socket >= 0) {
95-
close(sslclient->socket);
96-
sslclient->socket = -1;
97-
_connected = false;
98-
_peek = -1;
99-
_lastReadTimeout = 0;
100-
_lastWriteTimeout = 0;
101-
}
10294
stop_ssl_socket(sslclient, _CA_cert, _cert, _private_key);
95+
96+
_connected = false;
97+
_peek = -1;
98+
_lastReadTimeout = 0;
99+
_lastWriteTimeout = 0;
103100
}
104101

105102
int WiFiClientSecure::connect(IPAddress ip, uint16_t port)

0 commit comments

Comments
 (0)