|
| 1 | +/* |
| 2 | + HTTP Hashed Credential example |
| 3 | + Created April 27, 2019 by Tyler Moore. |
| 4 | + This example code is in the public domain. |
| 5 | +
|
| 6 | + This is a simple Arduino example to demonstrate a few simple techniques: |
| 7 | + 1. Creating a secure web server using ESP8266ESP8266WebServerSecure |
| 8 | + 2. Use of HTTP authentication on this secure server |
| 9 | + 3. A simple web interface to allow an authenticated user to change Credentials |
| 10 | + 4. Persisting those credentials through a reboot of the ESP by saving them to SPIFFS without storing them as plain text |
| 11 | +*/ |
| 12 | + |
| 13 | +#include <FS.h> |
| 14 | +#include <ESP8266WiFi.h> |
| 15 | +#include <ESP8266WebServerSecure.h> |
| 16 | + |
| 17 | +//Unfortunately it is not possible to have persistent WiFi credentials stored as anything but plain text. Obfuscation would be the only feasible barrier. |
| 18 | +#ifndef STASSID |
| 19 | +#define STASSID "your-ssid" |
| 20 | +#define STAPSK "your-password" |
| 21 | +#endif |
| 22 | + |
| 23 | +const char* ssid = STASSID; |
| 24 | +const char* wifi_pw = STAPSK; |
| 25 | + |
| 26 | +const String file_credentials = R"(/credentials.txt)"; //SPIFFS file name for the saved credentials |
| 27 | +const String change_creds = "changecreds"; //address for a credential change |
| 28 | + |
| 29 | +//The ESP8266WebServerSecure requires an encryption certificate and matching key. |
| 30 | +//These can generated with the bash script available in the ESP8266 Arduino repository. |
| 31 | +//These values can be used for testing but are available publicly so should not be used in production. |
| 32 | +static const char serverCert[] PROGMEM = R"EOF( |
| 33 | +-----BEGIN CERTIFICATE----- |
| 34 | +MIIDSzCCAjMCCQD2ahcfZAwXxDANBgkqhkiG9w0BAQsFADCBiTELMAkGA1UEBhMC |
| 35 | +VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU9yYW5nZSBDb3VudHkx |
| 36 | +EDAOBgNVBAoMB1ByaXZhZG8xGjAYBgNVBAMMEXNlcnZlci56bGFiZWwuY29tMR8w |
| 37 | +HQYJKoZIhvcNAQkBFhBlYXJsZUB6bGFiZWwuY29tMB4XDTE4MDMwNjA1NDg0NFoX |
| 38 | +DTE5MDMwNjA1NDg0NFowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3Rh |
| 39 | +dGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZI |
| 40 | +hvcNAQEBBQADggEPADCCAQoCggEBAPVKBwbZ+KDSl40YCDkP6y8Sv4iNGvEOZg8Y |
| 41 | +X7sGvf/xZH7UiCBWPFIRpNmDSaZ3yjsmFqm6sLiYSGSdrBCFqdt9NTp2r7hga6Sj |
| 42 | +oASSZY4B9pf+GblDy5m10KDx90BFKXdPMCLT+o76Nx9PpCvw13A848wHNG3bpBgI |
| 43 | +t+w/vJCX3bkRn8yEYAU6GdMbYe7v446hX3kY5UmgeJFr9xz1kq6AzYrMt/UHhNzO |
| 44 | +S+QckJaY0OGWvmTNspY3xCbbFtIDkCdBS8CZAw+itnofvnWWKQEXlt6otPh5njwy |
| 45 | ++O1t/Q+Z7OMDYQaH02IQx3188/kW3FzOY32knER1uzjmRO+jhA8CAwEAATANBgkq |
| 46 | +hkiG9w0BAQsFAAOCAQEAnDrROGRETB0woIcI1+acY1yRq4yAcH2/hdq2MoM+DCyM |
| 47 | +E8CJaOznGR9ND0ImWpTZqomHOUkOBpvu7u315blQZcLbL1LfHJGRTCHVhvVrcyEb |
| 48 | +fWTnRtAQdlirUm/obwXIitoz64VSbIVzcqqfg9C6ZREB9JbEX98/9Wp2gVY+31oC |
| 49 | +JfUvYadSYxh3nblvA4OL+iEZiW8NE3hbW6WPXxvS7Euge0uWMPc4uEcnsE0ZVG3m |
| 50 | ++TGimzSdeWDvGBRWZHXczC2zD4aoE5vrl+GD2i++c6yjL/otHfYyUpzUfbI2hMAA |
| 51 | +5tAF1D5vAAwA8nfPysumlLsIjohJZo4lgnhB++AlOg== |
| 52 | +-----END CERTIFICATE----- |
| 53 | +)EOF"; |
| 54 | +static const char serverKey[] PROGMEM = R"EOF( |
| 55 | +-----BEGIN RSA PRIVATE KEY----- |
| 56 | +MIIEpQIBAAKCAQEA9UoHBtn4oNKXjRgIOQ/rLxK/iI0a8Q5mDxhfuwa9//FkftSI |
| 57 | +IFY8UhGk2YNJpnfKOyYWqbqwuJhIZJ2sEIWp2301OnavuGBrpKOgBJJljgH2l/4Z |
| 58 | +uUPLmbXQoPH3QEUpd08wItP6jvo3H0+kK/DXcDzjzAc0bdukGAi37D+8kJfduRGf |
| 59 | +zIRgBToZ0xth7u/jjqFfeRjlSaB4kWv3HPWSroDNisy39QeE3M5L5ByQlpjQ4Za+ |
| 60 | +ZM2yljfEJtsW0gOQJ0FLwJkDD6K2eh++dZYpAReW3qi0+HmePDL47W39D5ns4wNh |
| 61 | +BofTYhDHfXzz+RbcXM5jfaScRHW7OOZE76OEDwIDAQABAoIBAQDKov5NFbNFQNR8 |
| 62 | +djcM1O7Is6dRaqiwLeH4ZH1pZ3d9QnFwKanPdQ5eCj9yhfhJMrr5xEyCqT0nMn7T |
| 63 | +yEIGYDXjontfsf8WxWkH2TjvrfWBrHOIOx4LJEvFzyLsYxiMmtZXvy6YByD+Dw2M |
| 64 | +q2GH/24rRdI2klkozIOyazluTXU8yOsSGxHr/aOa9/sZISgLmaGOOuKI/3Zqjdhr |
| 65 | +eHeSqoQFt3xXa8jw01YubQUDw/4cv9rk2ytTdAoQUimiKtgtjsggpP1LTq4xcuqN |
| 66 | +d4jWhTcnorWpbD2cVLxrEbnSR3VuBCJEZv5axg5ZPxLEnlcId8vMtvTRb5nzzszn |
| 67 | +geYUWDPhAoGBAPyKVNqqwQl44oIeiuRM2FYenMt4voVaz3ExJX2JysrG0jtCPv+Y |
| 68 | +84R6Cv3nfITz3EZDWp5sW3OwoGr77lF7Tv9tD6BptEmgBeuca3SHIdhG2MR+tLyx |
| 69 | +/tkIAarxQcTGsZaSqra3gXOJCMz9h2P5dxpdU+0yeMmOEnAqgQ8qtNBfAoGBAPim |
| 70 | +RAtnrd0WSlCgqVGYFCvDh1kD5QTNbZc+1PcBHbVV45EmJ2fLXnlDeplIZJdYxmzu |
| 71 | +DMOxZBYgfeLY9exje00eZJNSj/csjJQqiRftrbvYY7m5njX1kM5K8x4HlynQTDkg |
| 72 | +rtKO0YZJxxmjRTbFGMegh1SLlFLRIMtehNhOgipRAoGBAPnEEpJGCS9GGLfaX0HW |
| 73 | +YqwiEK8Il12q57mqgsq7ag7NPwWOymHesxHV5mMh/Dw+NyBi4xAGWRh9mtrUmeqK |
| 74 | +iyICik773Gxo0RIqnPgd4jJWN3N3YWeynzulOIkJnSNx5BforOCTc3uCD2s2YB5X |
| 75 | +jx1LKoNQxLeLRN8cmpIWicf/AoGBANjRSsZTKwV9WWIDJoHyxav/vPb+8WYFp8lZ |
| 76 | +zaRxQbGM6nn4NiZI7OF62N3uhWB/1c7IqTK/bVHqFTuJCrCNcsgld3gLZ2QWYaMV |
| 77 | +kCPgaj1BjHw4AmB0+EcajfKilcqtSroJ6MfMJ6IclVOizkjbByeTsE4lxDmPCDSt |
| 78 | +/9MKanBxAoGAY9xo741Pn9WUxDyRplww606ccdNf/ksHWNc/Y2B5SPwxxSnIq8nO |
| 79 | +j01SmsCUYVFAgZVOTiiycakjYLzxlc6p8BxSVqy6LlJqn95N8OXoQ+bkwUux/ekg |
| 80 | +gz5JWYhbD6c38khSzJb0pNXCo3EuYAVa36kDM96k1BtWuhRS10Q1VXk= |
| 81 | +-----END RSA PRIVATE KEY----- |
| 82 | +)EOF"; |
| 83 | + |
| 84 | +ESP8266WebServerSecure server(443); |
| 85 | + |
| 86 | +//These are temporary credentials that will only be used if none are found saved in SPIFFS. |
| 87 | +String login = "admin"; |
| 88 | +const String realm = "global"; |
| 89 | +String H1 = ""; |
| 90 | +String authentication_failed = "User authentication has failed."; |
| 91 | + |
| 92 | +void setup() { |
| 93 | + Serial.begin(115200); |
| 94 | + |
| 95 | + //Initialize SPIFFS to save credentials |
| 96 | + if(!SPIFFS.begin()){ |
| 97 | + Serial.println("SPIFFS initialization error, programmer flash configured?"); |
| 98 | + ESP.restart(); |
| 99 | + } |
| 100 | + |
| 101 | + //Attempt to load credentials. If the file does not yet exist, they will be set to the default values above |
| 102 | + loadcredentials(); |
| 103 | + |
| 104 | + //Initialize wifi |
| 105 | + WiFi.mode(WIFI_STA); |
| 106 | + WiFi.begin(ssid, wifi_pw); |
| 107 | + if (WiFi.waitForConnectResult() != WL_CONNECTED) { |
| 108 | + Serial.println("WiFi Connect Failed! Rebooting..."); |
| 109 | + delay(1000); |
| 110 | + ESP.restart(); |
| 111 | + } |
| 112 | + |
| 113 | + server.setRSACert(new BearSSL::X509List(serverCert), new BearSSL::PrivateKey(serverKey)); |
| 114 | + server.on("/",showcredentialpage); //for this simple example, just show a simple page for changing credentials at the root |
| 115 | + server.on("/" + change_creds,handlecredentialchange); //handles submission of credentials from the client |
| 116 | + server.onNotFound(redirect); |
| 117 | + server.begin(); |
| 118 | + |
| 119 | + Serial.print("Open https://"); |
| 120 | + Serial.print(WiFi.localIP()); |
| 121 | + Serial.println("/ in your browser to see it working"); |
| 122 | +} |
| 123 | + |
| 124 | +void loop() { |
| 125 | + yield(); |
| 126 | + server.handleClient(); |
| 127 | +} |
| 128 | + |
| 129 | +//This function redirects home |
| 130 | +void redirect(){ |
| 131 | + String url = "https://" + WiFi.localIP().toString(); |
| 132 | + Serial.println("Redirect called. Redirecting to " + url); |
| 133 | + server.sendHeader("Location", url, true); |
| 134 | + Serial.println("Header sent."); |
| 135 | + server.send( 302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves. |
| 136 | + Serial.println("Empty page sent."); |
| 137 | + server.client().stop(); // Stop is needed because we sent no content length |
| 138 | + Serial.println("Client stopped."); |
| 139 | +} |
| 140 | + |
| 141 | +//This function checks whether the current session has been authenticated. If not, a request for credentials is sent. |
| 142 | +bool session_authenticated() { |
| 143 | + Serial.println("Checking authentication."); |
| 144 | + if (server.authenticateDigest(login,H1)) { |
| 145 | + Serial.println("Authentication confirmed."); |
| 146 | + return true; |
| 147 | + } else { |
| 148 | + Serial.println("Not authenticated. Requesting credentials."); |
| 149 | + server.requestAuthentication(DIGEST_AUTH,realm.c_str(),authentication_failed); |
| 150 | + redirect(); |
| 151 | + return false; |
| 152 | + } |
| 153 | +} |
| 154 | + |
| 155 | +//This function sends a simple webpage for changing login credentials to the client |
| 156 | +void showcredentialpage(){ |
| 157 | + Serial.println("Show credential page called."); |
| 158 | + if(!session_authenticated()){ |
| 159 | + return; |
| 160 | + } |
| 161 | + |
| 162 | + Serial.println("Forming credential modification page."); |
| 163 | + |
| 164 | + String page; |
| 165 | + page = R"(<html>)"; |
| 166 | + |
| 167 | + page+= |
| 168 | + R"( |
| 169 | + <h2>Login Credentials</h2><br> |
| 170 | +
|
| 171 | + <form action=")" + change_creds + R"(" method="post"> |
| 172 | + Login:<br> |
| 173 | + <input type="text" name="login"><br> |
| 174 | + Password:<br> |
| 175 | + <input type="password" name="password"><br> |
| 176 | + Confirm Password:<br> |
| 177 | + <input type="password" name="password_duplicate"><br> |
| 178 | + <p><button type="submit" name="newcredentials">Change Credentials</button></p> |
| 179 | + </form><br> |
| 180 | + )" |
| 181 | + ; |
| 182 | + |
| 183 | + page += R"(</html>)"; |
| 184 | + |
| 185 | + Serial.println("Sending credential modification page."); |
| 186 | + |
| 187 | + server.send(200, "text/html", page); |
| 188 | +} |
| 189 | + |
| 190 | +//Saves credentials to SPIFFS |
| 191 | +void savecredentials(String new_login, String new_password) |
| 192 | +{ |
| 193 | + //Set global variables to new values |
| 194 | + login=new_login; |
| 195 | + H1=ESP8266WebServer::credentialHash(new_login,realm,new_password); |
| 196 | + |
| 197 | + //Save new values to SPIFFS for loading on next reboot |
| 198 | + Serial.println("Saving credentials."); |
| 199 | + File f=SPIFFS.open(file_credentials,"w"); //open as a brand new file, discard old contents |
| 200 | + if(f){ |
| 201 | + Serial.println("Modifying credentials in file system."); |
| 202 | + f.println(login); |
| 203 | + f.println(H1); |
| 204 | + Serial.println("Credentials written."); |
| 205 | + f.close(); |
| 206 | + Serial.println("File closed."); |
| 207 | + } |
| 208 | + Serial.println("Credentials saved."); |
| 209 | +} |
| 210 | + |
| 211 | +//loads credentials from SPIFFS |
| 212 | +void loadcredentials() |
| 213 | +{ |
| 214 | + Serial.println("Searching for credentials."); |
| 215 | + File f; |
| 216 | + f=SPIFFS.open(file_credentials,"r"); |
| 217 | + if(f){ |
| 218 | + Serial.println("Loading credentials from file system."); |
| 219 | + String mod=f.readString(); //read the file to a String |
| 220 | + int index_1=mod.indexOf('\n',0); //locate the first line break |
| 221 | + int index_2=mod.indexOf('\n',index_1+1); //locate the second line break |
| 222 | + login=mod.substring(0,index_1-1); //get the first line (excluding the line break) |
| 223 | + H1=mod.substring(index_1+1,index_2-1); //get the second line (excluding the line break) |
| 224 | + f.close(); |
| 225 | + } else { |
| 226 | + String default_login = "admin"; |
| 227 | + String default_password = "changeme"; |
| 228 | + Serial.println("None found. Setting to default credentials."); |
| 229 | + Serial.println("user:" + default_login); |
| 230 | + Serial.println("password:" + default_password); |
| 231 | + login=default_login; |
| 232 | + H1=ESP8266WebServer::credentialHash(default_login,realm,default_password); |
| 233 | + } |
| 234 | +} |
| 235 | + |
| 236 | +//This function handles a credential change from a client. |
| 237 | +void handlecredentialchange() { |
| 238 | + Serial.println("Handle credential change called."); |
| 239 | + if(!session_authenticated()){ |
| 240 | + return; |
| 241 | + } |
| 242 | + |
| 243 | + Serial.println("Handling credential change request from client."); |
| 244 | + |
| 245 | + String login = server.arg("login"); |
| 246 | + String pw1 = server.arg("password"); |
| 247 | + String pw2 = server.arg("password_duplicate"); |
| 248 | + |
| 249 | + if(login != "" && pw1 != "" && pw1 == pw2){ |
| 250 | + |
| 251 | + savecredentials(login,pw1); |
| 252 | + server.send(200, "text/plain", "Credentials updated"); |
| 253 | + redirect(); |
| 254 | + } else { |
| 255 | + server.send(200, "text/plain", "Malformed credentials"); |
| 256 | + redirect(); |
| 257 | + } |
| 258 | +} |
0 commit comments