Skip to content

Commit 4337f38

Browse files
committed
feature: create a Trust on First Use example the quell the increasingly common copy & paste of the insecure approach making it to production
1 parent e4d6a8a commit 4337f38

File tree

2 files changed

+277
-0
lines changed

2 files changed

+277
-0
lines changed

libraries/WiFiClientSecure/examples/WiFiClientInsecure/WiFiClientInsecure.ino

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
#include <WiFiClientSecure.h>
22

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

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
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+
things 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 to GPIO 4 */
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+
66+
// Utility function; checks if a given buffer is entirly
67+
// with with 0 bytes over its full length. Returns 0 on
68+
// succes; a non zero value on fail.
69+
//
70+
static int memcmpzero(unsigned char * ptr, size_t len) {
71+
while (len--) if (*ptr++) return -1;
72+
return 0;
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+
//
214+
client.setInsecure();//skip verification
215+
216+
if (!client.connect(server, 443)) {
217+
Serial.println("Connection failed!");
218+
client.stop();
219+
return false;
220+
};
221+
222+
// Now that we're connected - we can check that we have
223+
// end to end trust - by comparing the fingerprint we (now)
224+
// see (of the server certificate) to the one we have stored
225+
// in our EEPROM as part of an earlier trust-on-first use.
226+
//
227+
uint8_t fingerprint_remote[32];
228+
if (!client.getFingerprintSHA256(fingerprint_remote)) {
229+
Serial.println("Failed to get the fingerprint of the server");
230+
client.stop();
231+
return false;
232+
}
233+
if (memcmp(fingerprint_remote, fingerprint_tofu, 32)) {
234+
Serial.println("TOFU fingerprint not the same as the one from the server.");
235+
Serial.print("TOFU : SHA256=");
236+
printSHA256(fingerprint_tofu);
237+
Serial.print("Remote: SHA256=");
238+
printSHA256(fingerprint_remote);
239+
Serial.println(" : NOT identical -- Aborting!");
240+
client.stop();
241+
return false;
242+
};
243+
244+
Serial.println("All well - you are talking to the same server as\n"
245+
"when you set up TOFU. So we can now do a GET.\n\n");
246+
247+
client.println("GET /a/check HTTP/1.0");
248+
client.print("Host: " ); client.println(server);
249+
client.println("Connection: close");
250+
client.println();
251+
252+
bool inhdr = true;
253+
while (client.connected()) {
254+
String line = client.readStringUntil('\n');
255+
Serial.print(line);
256+
if (inhdr && line == "\r") {
257+
inhdr = false;
258+
Serial.println("-- headers received. Payload follows\n\n");
259+
}
260+
}
261+
Serial.println("\n\n-- Payload ended.");
262+
client.stop();
263+
return true;
264+
}
265+
266+
void loop() {
267+
delay(-1);
268+
}

0 commit comments

Comments
 (0)