-
Notifications
You must be signed in to change notification settings - Fork 7.6k
feature: create a Trust on First Use example #9103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
4337f38
feature: create a Trust on First Use example the quell the increasing…
dirkx ae1a1b2
Merge branch 'master' into feat_tofu
me-no-dev 7c6c58f
Quell CI/CD runs on non-WiFi supporting hardare
dirkx 0ac8c9c
Merge branch 'feat_tofu' of github.com:dirkx/arduino-esp32 into feat_…
dirkx d92be06
Update libraries/WiFiClientSecure/examples/WiFiClientInsecure/WiFiCli…
dirkx cb1704a
Update libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/…
dirkx 5a0adfd
Update libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/…
dirkx 79a390d
Update libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/…
dirkx 1f7daa0
Update libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/…
dirkx 0118e15
Update libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/…
dirkx 6758e4d
Update libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/…
dirkx e529fa9
Update libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/…
dirkx 8f21a07
Various things can all stop_ssl_socket() which sets the socket to -1;…
dirkx 9f72a9c
Unitialized NVRAM/EEPROM is actual set to 0xFF; so adjust for this. A…
dirkx fd2d0f4
Merge github.com:espressif/arduino-esp31 into feat_tofu
dirkx da537c7
Merge branch 'feat_tofu' of github.com:dirkx/arduino-esp31 into feat_…
dirkx a502c60
Update libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/…
lucasssvaz 46ef39a
Merge branch 'master' into feat_tofu
lucasssvaz bf16cd3
Merge branch 'master' into feat_tofu
lucasssvaz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
8 changes: 8 additions & 0 deletions
8
libraries/WiFiClientSecure/examples/WiFiClientInsecure/WiFiClientInsecure.ino
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
264 changes: 264 additions & 0 deletions
264
libraries/WiFiClientSecure/examples/WiFiClientTrustOnFirstUse/WiFiClientTrustOnFirstUse.ino
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,264 @@ | ||
/* 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() {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.