Skip to content
This repository has been archived by the owner on Jan 11, 2020. It is now read-only.

Secure wifi ap mode #22

Merged
merged 8 commits into from
Jan 31, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 31 additions & 10 deletions Basecamp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@
namespace {
const constexpr uint16_t defaultThreadStackSize = 3072;
const constexpr UBaseType_t defaultThreadPriority = 0;
// Default length for access point mode password
const constexpr unsigned defaultApSecretLength = 8;
}

Basecamp::Basecamp()
Basecamp::Basecamp(SetupModeWifiEncryption setupModeWifiEncryption)
: configuration(String{"/basecamp.json"})
, setupModeWifiEncryption_(setupModeWifiEncryption)
{

}

/**
Expand Down Expand Up @@ -49,7 +51,6 @@ String Basecamp::_generateHostname()
/**
* This is the initialisation function for the Basecamp class.
*/

bool Basecamp::begin()
{
// Enable serial output
Expand All @@ -75,16 +76,30 @@ bool Basecamp::begin()

#ifndef BASECAMP_NOWIFI

// If there is no access point secret set yet, generate one and save it.
// It will survive the default config reset.
if (!configuration.isKeySet(ConfigurationKey::accessPointSecret))
{
Serial.println("Generating access point secret...");
auto apSecret = wifi.generateRandomSecret(defaultApSecretLength);
configuration.set(ConfigurationKey::accessPointSecret, apSecret);
configuration.save();
}

Serial.printf("Secret: %s\n", configuration.get(ConfigurationKey::accessPointSecret).c_str());

// Initialize Wifi with the stored configuration data.
wifi.begin(
configuration.get("WifiEssid"), // The (E)SSID or WiFi-Name
configuration.get("WifiPassword"), // The WiFi password
configuration.get("WifiConfigured"), // Has the WiFi been configured
hostname // The system hostname to use for DHCP
);
hostname, // The system hostname to use for DHCP
(setupModeWifiEncryption_ == SetupModeWifiEncryption::none)?"":configuration.get(ConfigurationKey::accessPointSecret)
);

// Get WiFI MAC
mac = wifi.getSoftwareMacAddress(":");
DEBUG_PRINTLN(showSystemInfo().c_str());
Serial.println(showSystemInfo().c_str());
#endif
#ifndef BASECAMP_NOMQTT
// Check if MQTT has been disabled by the user
Expand Down Expand Up @@ -228,8 +243,8 @@ void Basecamp::MqttHandling(void *mqttPointer)

#ifdef DNSServer_h
// This is a task that handles DNS requests from clients
void Basecamp::DnsHandling(void * dnsServerPointer) {

void Basecamp::DnsHandling(void * dnsServerPointer)
{
DNSServer * dnsServer = (DNSServer *) dnsServerPointer;
while(1) {
// handle each request
Expand All @@ -239,7 +254,6 @@ void Basecamp::DnsHandling(void * dnsServerPointer) {
};
#endif


// This function checks the reset reason returned by the ESP and resets the configuration if neccessary.
// It counts all system reboots that occured by power cycles or button resets.
// If the ESP32 receives an IP the boot counts as successful and the counter will be reset by Basecamps
Expand Down Expand Up @@ -368,7 +382,14 @@ void Basecamp::OTAHandling(void * OTAParams) {
String Basecamp::showSystemInfo() {
std::ostringstream info;
info << "MAC-Address: " << mac.c_str();
info << ", Hardware MAC: " << wifi.getHardwareMacAddress(":").c_str();
info << ", Hardware MAC: " << wifi.getHardwareMacAddress(":").c_str() << std::endl;

if (configuration.isKeySet(ConfigurationKey::accessPointSecret)) {
info << "*******************************************" << std::endl;
info << "* ACCESS POINT PASSWORD: ";
info << configuration.get(ConfigurationKey::accessPointSecret).c_str() << std::endl;
info << "*******************************************" << std::endl;
}

return {info.str().c_str()};
}
10 changes: 9 additions & 1 deletion Basecamp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,14 @@

class Basecamp {
public:
Basecamp();
// How to handle encryption in setup mode (AP mode)
enum class SetupModeWifiEncryption
{
none, ///< Do not use WiFi encryption (open network)
secured, ///< Use ESP32 default encryption (WPA2 at this time)
};

Basecamp(Basecamp::SetupModeWifiEncryption setupModeWifiEncryption = Basecamp::SetupModeWifiEncryption::none);
~Basecamp() = default;
Configuration configuration;
Preferences preferences;
Expand Down Expand Up @@ -67,5 +74,6 @@ class Basecamp {
private:
// TODO: Functionname is misleading
String _generateHostname();
SetupModeWifiEncryption setupModeWifiEncryption_;
};
#endif
58 changes: 55 additions & 3 deletions Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ bool Configuration::save() {
return true;
}


void Configuration::set(String key, String value) {
std::ostringstream debug;
debug << "Settting " << key.c_str() << " to " << value.c_str() << "(was " << get(key).c_str() << ")";
Expand All @@ -87,7 +86,13 @@ void Configuration::set(String key, String value) {
}
}

const String &Configuration::get(String key) const {
void Configuration::set(ConfigurationKey key, String value)
{
set(getKeyName(key), std::move(value));
}

const String &Configuration::get(String key) const
{
auto found = configuration.find(key);
if (found != configuration.end()) {
std::ostringstream debug;
Expand All @@ -101,8 +106,55 @@ const String &Configuration::get(String key) const {
return noResult_;
}

void Configuration::reset() {
const String &Configuration::get(ConfigurationKey key) const
{
return get(getKeyName(key));
}

bool Configuration::keyExists(const String& key) const
{
return (configuration.find(key) != configuration.end());
}

bool Configuration::keyExists(ConfigurationKey key) const
{
return (configuration.find(getKeyName(key)) != configuration.end());
}

bool Configuration::isKeySet(ConfigurationKey key) const
{
auto found = configuration.find(getKeyName(key));
if (found == configuration.end())
{
return false;
}

return (found->second.length() > 0);
}

void Configuration::reset()
{
configuration.clear();
this->save();
this->load();
}

void Configuration::resetExcept(const std::list<ConfigurationKey> &keysToPreserve)
{
std::map<ConfigurationKey, String> preservedKeys;
for (const auto &key : keysToPreserve) {
if (keyExists(key)) {
// Make a copy of the old value
preservedKeys[key] = get(key);
}
}

configuration.clear();

for (const auto &key : preservedKeys) {
set(key.first, key.second);
}

this->save();
this->load();
}
Expand Down
44 changes: 44 additions & 0 deletions Configuration.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,69 @@
#define Configuration_h

#include <sstream>
#include <list>
#include <map>

#include <ArduinoJson.h>
#include <FS.h>
#include <SPIFFS.h>
#include "debug.hpp"

// TODO: Extend with all known keys
enum class ConfigurationKey {
accessPointSecret,
};

// TODO: Extend with all known keys
static const String getKeyName(ConfigurationKey key)
{
// This automatically will break the compiler if a known key has been forgotten
// (if the warnings are turned on exactly...)
switch (key)
{
case ConfigurationKey::accessPointSecret:
return "APSecret";
break;
}
}

class Configuration {
public:
explicit Configuration(String filename);
~Configuration() = default;

const String& getKey(ConfigurationKey configKey) const;

bool load();
bool save();
void dump();

// Returns true if the key 'key' exists
bool keyExists(const String& key) const;

// Returns true if the key 'key' exists
bool keyExists(ConfigurationKey key) const;

// Returns true if the key 'key' exists and is not empty
bool isKeySet(ConfigurationKey key) const;

// Reset the whole configuration
void reset();

// Reset everything except the AP secret
void resetExcept(const std::list<ConfigurationKey> &keysToPreserve);

// FIXME: Get rid of every direct access ("name") set() and get()
// to minimize the rist of unknown-key usage. Move to private.
void set(String key, String value);
// FIXME: use this instead
void set(ConfigurationKey key, String value);

// FIXME: Get rid of every direct access ("name") set() and get()
// to minimize the rist of unknown-key usage. Move to private.
const String& get(String key) const;
// FIXME: use this instead
const String& get(ConfigurationKey key) const;

struct cmp_str
{
Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ This library has few dependencies:

Exhaustive documentation will provided in the next few weeks. An example can be found inside the example folder.

### First Setup:
At the first start - when you initially flash the device, the ESP32 will generate an
unique password which is displayed at the debug console upon every start. In setup mode (when the ESP32 is acting as
an access point for setup), the password for the "ESP_$macOfEsp32" wifi network will be set to this value. It will never change,
except the configuration gets broken - then a new password will be generated.

## Basic example

```cpp
Expand All @@ -31,11 +37,11 @@ void setup() {
iot.begin();
//The mqtt object is an instance of Async MQTT Client. See it's documentation for details.
iot.mqtt.subscribe("test/lol",2);

//Use the web object to add elements to the interface
iot.web.addInterfaceElement("color", "input", "", "#configform", "LampColor");
iot.web.setInterfaceElementAttribute("color", "type", "text");

}

void loop() {
Expand Down
35 changes: 33 additions & 2 deletions WifiControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@
#include "debug.hpp"
#include "Basecamp.hpp"

void WifiControl::begin(String essid, String password, String configured, String hostname)
namespace {
// Minumum access point secret length to be generated (8 is min for ESP32)
const constexpr unsigned minApSecretLength = 8;
}

void WifiControl::begin(String essid, String password, String configured,
String hostname, String apSecret)
{
DEBUG_PRINTLN("Connecting to Wifi");

Expand All @@ -35,7 +41,14 @@ void WifiControl::begin(String essid, String password, String configured, String
DEBUG_PRINTF("Starting Wifi AP '%s'", _wifiAPName);

WiFi.mode(WIFI_AP_STA);
WiFi.softAP(_wifiAPName.c_str());
if (apSecret.length() > 0) {
// Start with password protection
Serial.printf("Starting AP with password %s\n", apSecret.c_str());
WiFi.softAP(_wifiAPName.c_str(), apSecret.c_str());
} else {
// Start without password protection
WiFi.softAP(_wifiAPName.c_str());
}
}
}

Expand Down Expand Up @@ -106,3 +119,21 @@ String WifiControl::getSoftwareMacAddress(const String& delimiter)
WiFi.macAddress(rawMac);
return format6Bytes(rawMac, delimiter);
}

String WifiControl::generateRandomSecret(unsigned length) const
{
// There is no "O" (Oh) to reduce confusion
const String validChars{"abcdefghjkmnopqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ23456789.-,:$/"};
String returnValue;

unsigned useLength = (length < minApSecretLength)?minApSecretLength:length;
returnValue.reserve(useLength);

for (unsigned i = 0; i < useLength; i++)
{
auto randomValue = validChars[(esp_random() % validChars.length())];
returnValue += randomValue;
}

return returnValue;
}
6 changes: 5 additions & 1 deletion WifiControl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@ class WifiControl {
WifiControl(){};
bool connect();
bool disconnect();
void begin(String essid, String password = "", String configured = "False", String hostname = "BasecampDevice");

void begin(String essid, String password = "", String configured = "False",
String hostname = "BasecampDevice", String apSecret="");
IPAddress getIP();
IPAddress getSoftAPIP();
int status();
static void WiFiEvent(WiFiEvent_t event);

String generateRandomSecret(unsigned length) const;

/*
Returns the MAC Address of the wifi adapter in hexadecimal form, optionally delimited
by a given delimiter which is inserted between every hex-representation.
Expand Down
Loading