/* HTTP Hashed Credential example Created April 27, 2019 by Tyler Moore. This example code is in the public domain. This is a simple Arduino example to demonstrate a few simple techniques: 1. Creating a secure web server using ESP8266ESP8266WebServerSecure 2. Use of HTTP authentication on this secure server 3. A simple web interface to allow an authenticated user to change Credentials 4. Persisting those credentials through a reboot of the ESP by saving them to LittleFS without storing them as plain text */ #include #include #include #include // Unfortunately it is not possible to have persistent WiFi credentials stored as anything but plain text. Obfuscation would be the only feasible barrier. #ifndef STASSID #define STASSID "your-ssid" #define STAPSK "your-password" #endif const char* ssid = STASSID; const char* wifi_pw = STAPSK; const String file_credentials = R"(/credentials.txt)"; // LittleFS file name for the saved credentials const String change_creds = "changecreds"; // Address for a credential change // The ESP8266WebServerSecure requires an encryption certificate and matching key. // These can generated with the bash script available in the ESP8266 Arduino repository. // These values can be used for testing but are available publicly so should not be used in production. static const char serverCert[] PROGMEM = R"EOF( -----BEGIN CERTIFICATE----- MIIDSzCCAjMCCQD2ahcfZAwXxDANBgkqhkiG9w0BAQsFADCBiTELMAkGA1UEBhMC VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU9yYW5nZSBDb3VudHkx EDAOBgNVBAoMB1ByaXZhZG8xGjAYBgNVBAMMEXNlcnZlci56bGFiZWwuY29tMR8w HQYJKoZIhvcNAQkBFhBlYXJsZUB6bGFiZWwuY29tMB4XDTE4MDMwNjA1NDg0NFoX DTE5MDMwNjA1NDg0NFowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3Rh dGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAPVKBwbZ+KDSl40YCDkP6y8Sv4iNGvEOZg8Y X7sGvf/xZH7UiCBWPFIRpNmDSaZ3yjsmFqm6sLiYSGSdrBCFqdt9NTp2r7hga6Sj oASSZY4B9pf+GblDy5m10KDx90BFKXdPMCLT+o76Nx9PpCvw13A848wHNG3bpBgI t+w/vJCX3bkRn8yEYAU6GdMbYe7v446hX3kY5UmgeJFr9xz1kq6AzYrMt/UHhNzO S+QckJaY0OGWvmTNspY3xCbbFtIDkCdBS8CZAw+itnofvnWWKQEXlt6otPh5njwy +O1t/Q+Z7OMDYQaH02IQx3188/kW3FzOY32knER1uzjmRO+jhA8CAwEAATANBgkq hkiG9w0BAQsFAAOCAQEAnDrROGRETB0woIcI1+acY1yRq4yAcH2/hdq2MoM+DCyM E8CJaOznGR9ND0ImWpTZqomHOUkOBpvu7u315blQZcLbL1LfHJGRTCHVhvVrcyEb fWTnRtAQdlirUm/obwXIitoz64VSbIVzcqqfg9C6ZREB9JbEX98/9Wp2gVY+31oC JfUvYadSYxh3nblvA4OL+iEZiW8NE3hbW6WPXxvS7Euge0uWMPc4uEcnsE0ZVG3m +TGimzSdeWDvGBRWZHXczC2zD4aoE5vrl+GD2i++c6yjL/otHfYyUpzUfbI2hMAA 5tAF1D5vAAwA8nfPysumlLsIjohJZo4lgnhB++AlOg== -----END CERTIFICATE----- )EOF"; static const char serverKey[] PROGMEM = R"EOF( -----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEA9UoHBtn4oNKXjRgIOQ/rLxK/iI0a8Q5mDxhfuwa9//FkftSI IFY8UhGk2YNJpnfKOyYWqbqwuJhIZJ2sEIWp2301OnavuGBrpKOgBJJljgH2l/4Z uUPLmbXQoPH3QEUpd08wItP6jvo3H0+kK/DXcDzjzAc0bdukGAi37D+8kJfduRGf zIRgBToZ0xth7u/jjqFfeRjlSaB4kWv3HPWSroDNisy39QeE3M5L5ByQlpjQ4Za+ ZM2yljfEJtsW0gOQJ0FLwJkDD6K2eh++dZYpAReW3qi0+HmePDL47W39D5ns4wNh BofTYhDHfXzz+RbcXM5jfaScRHW7OOZE76OEDwIDAQABAoIBAQDKov5NFbNFQNR8 djcM1O7Is6dRaqiwLeH4ZH1pZ3d9QnFwKanPdQ5eCj9yhfhJMrr5xEyCqT0nMn7T yEIGYDXjontfsf8WxWkH2TjvrfWBrHOIOx4LJEvFzyLsYxiMmtZXvy6YByD+Dw2M q2GH/24rRdI2klkozIOyazluTXU8yOsSGxHr/aOa9/sZISgLmaGOOuKI/3Zqjdhr eHeSqoQFt3xXa8jw01YubQUDw/4cv9rk2ytTdAoQUimiKtgtjsggpP1LTq4xcuqN d4jWhTcnorWpbD2cVLxrEbnSR3VuBCJEZv5axg5ZPxLEnlcId8vMtvTRb5nzzszn geYUWDPhAoGBAPyKVNqqwQl44oIeiuRM2FYenMt4voVaz3ExJX2JysrG0jtCPv+Y 84R6Cv3nfITz3EZDWp5sW3OwoGr77lF7Tv9tD6BptEmgBeuca3SHIdhG2MR+tLyx /tkIAarxQcTGsZaSqra3gXOJCMz9h2P5dxpdU+0yeMmOEnAqgQ8qtNBfAoGBAPim RAtnrd0WSlCgqVGYFCvDh1kD5QTNbZc+1PcBHbVV45EmJ2fLXnlDeplIZJdYxmzu DMOxZBYgfeLY9exje00eZJNSj/csjJQqiRftrbvYY7m5njX1kM5K8x4HlynQTDkg rtKO0YZJxxmjRTbFGMegh1SLlFLRIMtehNhOgipRAoGBAPnEEpJGCS9GGLfaX0HW YqwiEK8Il12q57mqgsq7ag7NPwWOymHesxHV5mMh/Dw+NyBi4xAGWRh9mtrUmeqK iyICik773Gxo0RIqnPgd4jJWN3N3YWeynzulOIkJnSNx5BforOCTc3uCD2s2YB5X jx1LKoNQxLeLRN8cmpIWicf/AoGBANjRSsZTKwV9WWIDJoHyxav/vPb+8WYFp8lZ zaRxQbGM6nn4NiZI7OF62N3uhWB/1c7IqTK/bVHqFTuJCrCNcsgld3gLZ2QWYaMV kCPgaj1BjHw4AmB0+EcajfKilcqtSroJ6MfMJ6IclVOizkjbByeTsE4lxDmPCDSt /9MKanBxAoGAY9xo741Pn9WUxDyRplww606ccdNf/ksHWNc/Y2B5SPwxxSnIq8nO j01SmsCUYVFAgZVOTiiycakjYLzxlc6p8BxSVqy6LlJqn95N8OXoQ+bkwUux/ekg gz5JWYhbD6c38khSzJb0pNXCo3EuYAVa36kDM96k1BtWuhRS10Q1VXk= -----END RSA PRIVATE KEY----- )EOF"; ESP8266WebServerSecure server(443); // These are temporary credentials that will only be used if none are found saved in LittleFS. String login = "admin"; const String realm = "global"; String H1 = ""; String authentication_failed = "User authentication has failed."; void setup() { Serial.begin(115200); // Initialize LittleFS to save credentials if (!LittleFS.begin()) { Serial.println("LittleFS initialization error, programmer flash configured?"); ESP.restart(); } // Attempt to load credentials. If the file does not yet exist, they will be set to the default values above loadcredentials(); // Initialize wifi WiFi.mode(WIFI_STA); WiFi.begin(ssid, wifi_pw); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi Connect Failed! Rebooting..."); delay(1000); ESP.restart(); } server.getServer().setRSACert(new BearSSL::X509List(serverCert), new BearSSL::PrivateKey(serverKey)); server.on("/", showcredentialpage); // for this simple example, just show a simple page for changing credentials at the root server.on("/" + change_creds, handlecredentialchange); // handles submission of credentials from the client server.onNotFound(redirect); server.begin(); Serial.print("Open https://"); Serial.print(WiFi.localIP()); Serial.println("/ in your browser to see it working"); } void loop() { yield(); server.handleClient(); } // This function redirects home void redirect() { String url = "https://" + WiFi.localIP().toString(); Serial.println("Redirect called. Redirecting to " + url); server.sendHeader("Location", url, true); Serial.println("Header sent."); server.send(302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves. Serial.println("Empty page sent."); server.client().stop(); // Stop is needed because we sent no content length Serial.println("Client stopped."); } // This function checks whether the current session has been authenticated. If not, a request for credentials is sent. bool session_authenticated() { Serial.println("Checking authentication."); if (server.authenticateDigest(login, H1)) { Serial.println("Authentication confirmed."); return true; } else { Serial.println("Not authenticated. Requesting credentials."); server.requestAuthentication(DIGEST_AUTH, realm.c_str(), authentication_failed); redirect(); return false; } } // This function sends a simple webpage for changing login credentials to the client void showcredentialpage() { Serial.println("Show credential page called."); if (!session_authenticated()) { return; } Serial.println("Forming credential modification page."); String page; page = R"()"; page += R"(

Login Credentials


Login:

Password:

Confirm Password:


)"; page += R"()"; Serial.println("Sending credential modification page."); server.send(200, "text/html", page); } // Saves credentials to LittleFS void savecredentials(String new_login, String new_password) { // Set global variables to new values login = new_login; H1 = ESP8266WebServer::credentialHash(new_login, realm, new_password); // Save new values to LittleFS for loading on next reboot Serial.println("Saving credentials."); File f = LittleFS.open(file_credentials, "w"); // open as a brand new file, discard old contents if (f) { Serial.println("Modifying credentials in file system."); f.println(login); f.println(H1); Serial.println("Credentials written."); f.close(); Serial.println("File closed."); } Serial.println("Credentials saved."); } // loads credentials from LittleFS void loadcredentials() { Serial.println("Searching for credentials."); File f; f = LittleFS.open(file_credentials, "r"); if (f) { Serial.println("Loading credentials from file system."); String mod = f.readString(); // read the file to a String int index_1 = mod.indexOf('\n', 0); // locate the first line break int index_2 = mod.indexOf('\n', index_1 + 1); // locate the second line break login = mod.substring(0, index_1 - 1); // get the first line (excluding the line break) H1 = mod.substring(index_1 + 1, index_2 - 1); // get the second line (excluding the line break) f.close(); } else { String default_login = "admin"; String default_password = "changeme"; Serial.println("None found. Setting to default credentials."); Serial.println("user:" + default_login); Serial.println("password:" + default_password); login = default_login; H1 = ESP8266WebServer::credentialHash(default_login, realm, default_password); } } // This function handles a credential change from a client. void handlecredentialchange() { Serial.println("Handle credential change called."); if (!session_authenticated()) { return; } Serial.println("Handling credential change request from client."); String login = server.arg("login"); String pw1 = server.arg("password"); String pw2 = server.arg("password_duplicate"); if (login != "" && pw1 != "" && pw1 == pw2) { savecredentials(login, pw1); server.send(200, "text/plain", "Credentials updated"); redirect(); } else { server.send(200, "text/plain", "Malformed credentials"); redirect(); } }