2021-02-16 07:41:52 +00:00
|
|
|
#include "Channels.h"
|
|
|
|
#include "CryptoEngine.h"
|
2021-02-22 03:16:38 +00:00
|
|
|
#include "NodeDB.h"
|
2021-02-16 07:41:52 +00:00
|
|
|
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
|
|
/// 16 bytes of random PSK for our _public_ default channel that all devices power up on (AES128)
|
|
|
|
static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59,
|
|
|
|
0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0xbf};
|
|
|
|
|
|
|
|
Channels channels;
|
|
|
|
|
2021-02-23 02:10:35 +00:00
|
|
|
uint8_t xorHash(const uint8_t *p, size_t len)
|
2021-02-22 04:57:26 +00:00
|
|
|
{
|
|
|
|
uint8_t code = 0;
|
|
|
|
for (int i = 0; i < len; i++)
|
|
|
|
code ^= p[i];
|
|
|
|
return code;
|
|
|
|
}
|
|
|
|
|
2021-02-23 02:10:35 +00:00
|
|
|
/** Given a channel number, return the (0 to 255) hash for that channel.
|
|
|
|
* The hash is just an xor of the channel name followed by the channel PSK being used for encryption
|
|
|
|
* If no suitable channel could be found, return -1
|
|
|
|
*/
|
|
|
|
int16_t Channels::generateHash(ChannelIndex channelNum)
|
|
|
|
{
|
|
|
|
auto k = getKey(channelNum);
|
|
|
|
if (k.length < 0)
|
|
|
|
return -1; // invalid
|
|
|
|
else {
|
2021-02-23 06:35:34 +00:00
|
|
|
const char *name = getName(channelNum);
|
|
|
|
uint8_t h = xorHash((const uint8_t *) name, strlen(name));
|
2021-02-23 02:10:35 +00:00
|
|
|
|
|
|
|
h ^= xorHash(k.bytes, k.length);
|
|
|
|
|
|
|
|
return h;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-16 07:41:52 +00:00
|
|
|
/**
|
|
|
|
* Validate a channel, fixing any errors as needed
|
|
|
|
*/
|
2021-02-22 03:16:38 +00:00
|
|
|
Channel &Channels::fixupChannel(ChannelIndex chIndex)
|
2021-02-16 07:41:52 +00:00
|
|
|
{
|
2021-02-22 04:57:26 +00:00
|
|
|
Channel &ch = getByIndex(chIndex);
|
2021-02-16 07:41:52 +00:00
|
|
|
|
2021-02-22 03:16:38 +00:00
|
|
|
ch.index = chIndex; // Preinit the index so it be ready to share with the phone (we'll never change it later)
|
2021-02-16 07:41:52 +00:00
|
|
|
|
2021-02-22 03:16:38 +00:00
|
|
|
if (!ch.has_settings) {
|
2021-02-16 07:41:52 +00:00
|
|
|
// No settings! Must disable and skip
|
2021-02-22 03:16:38 +00:00
|
|
|
ch.role = Channel_Role_DISABLED;
|
|
|
|
memset(&ch.settings, 0, sizeof(ch.settings));
|
|
|
|
ch.has_settings = true;
|
2021-02-16 07:41:52 +00:00
|
|
|
} else {
|
2021-02-22 03:16:38 +00:00
|
|
|
ChannelSettings &channelSettings = ch.settings;
|
2021-02-16 07:41:52 +00:00
|
|
|
|
|
|
|
// Convert the old string "Default" to our new short representation
|
|
|
|
if (strcmp(channelSettings.name, "Default") == 0)
|
|
|
|
*channelSettings.name = '\0';
|
|
|
|
|
2021-02-22 04:57:26 +00:00
|
|
|
/* Convert any old usage of the defaultpsk into our new short representation.
|
2021-02-16 07:41:52 +00:00
|
|
|
if (channelSettings.psk.size == sizeof(defaultpsk) &&
|
|
|
|
memcmp(channelSettings.psk.bytes, defaultpsk, sizeof(defaultpsk)) == 0) {
|
|
|
|
*channelSettings.psk.bytes = 1;
|
|
|
|
channelSettings.psk.size = 1;
|
2021-02-22 04:57:26 +00:00
|
|
|
} */
|
2021-02-16 07:41:52 +00:00
|
|
|
}
|
|
|
|
|
2021-02-22 04:57:26 +00:00
|
|
|
hashes[chIndex] = generateHash(chIndex);
|
|
|
|
|
2021-02-22 03:16:38 +00:00
|
|
|
return ch;
|
2021-02-16 07:41:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Write a default channel to the specified channel index
|
|
|
|
*/
|
2021-02-22 03:16:38 +00:00
|
|
|
void Channels::initDefaultChannel(ChannelIndex chIndex)
|
2021-02-16 07:41:52 +00:00
|
|
|
{
|
2021-02-22 04:57:26 +00:00
|
|
|
Channel &ch = getByIndex(chIndex);
|
2021-02-22 03:16:38 +00:00
|
|
|
ChannelSettings &channelSettings = ch.settings;
|
2021-02-16 07:41:52 +00:00
|
|
|
|
|
|
|
// radioConfig.modem_config = RadioConfig_ModemConfig_Bw125Cr45Sf128; // medium range and fast
|
|
|
|
// channelSettings.modem_config = ChannelSettings_ModemConfig_Bw500Cr45Sf128; // short range and fast, but wide
|
|
|
|
// bandwidth so incompatible radios can talk together
|
|
|
|
channelSettings.modem_config = ChannelSettings_ModemConfig_Bw125Cr48Sf4096; // slow and long range
|
|
|
|
|
|
|
|
channelSettings.tx_power = 0; // default
|
|
|
|
uint8_t defaultpskIndex = 1;
|
|
|
|
channelSettings.psk.bytes[0] = defaultpskIndex;
|
|
|
|
channelSettings.psk.size = 1;
|
|
|
|
strcpy(channelSettings.name, "");
|
|
|
|
|
2021-02-22 03:16:38 +00:00
|
|
|
ch.has_settings = true;
|
|
|
|
ch.role = Channel_Role_PRIMARY;
|
2021-02-16 07:41:52 +00:00
|
|
|
}
|
|
|
|
|
2021-02-23 02:10:35 +00:00
|
|
|
CryptoKey Channels::getKey(ChannelIndex chIndex)
|
2021-02-16 07:41:52 +00:00
|
|
|
{
|
2021-02-22 04:57:26 +00:00
|
|
|
Channel &ch = getByIndex(chIndex);
|
2021-02-22 03:16:38 +00:00
|
|
|
ChannelSettings &channelSettings = ch.settings;
|
|
|
|
assert(ch.has_settings);
|
2021-02-16 07:41:52 +00:00
|
|
|
|
2021-02-23 02:10:35 +00:00
|
|
|
CryptoKey k;
|
|
|
|
memset(k.bytes, 0, sizeof(k.bytes)); // In case the user provided a short key, we want to pad the rest with zeros
|
|
|
|
|
|
|
|
if (ch.role == Channel_Role_DISABLED) {
|
|
|
|
k.length = -1; // invalid
|
|
|
|
} else {
|
|
|
|
memcpy(k.bytes, channelSettings.psk.bytes, channelSettings.psk.size);
|
|
|
|
k.length = channelSettings.psk.size;
|
|
|
|
if (k.length == 0) {
|
|
|
|
if (ch.role == Channel_Role_SECONDARY) {
|
|
|
|
DEBUG_MSG("Unset PSK for secondary channel %s. using primary key\n", ch.settings.name);
|
|
|
|
k = getKey(primaryIndex);
|
|
|
|
} else
|
|
|
|
DEBUG_MSG("Warning: User disabled encryption\n");
|
|
|
|
} else if (k.length == 1) {
|
|
|
|
// Convert the short single byte variants of psk into variant that can be used more generally
|
|
|
|
|
|
|
|
uint8_t pskIndex = k.bytes[0];
|
|
|
|
DEBUG_MSG("Expanding short PSK #%d\n", pskIndex);
|
|
|
|
if (pskIndex == 0)
|
|
|
|
k.length = 0; // Turn off encryption
|
|
|
|
else {
|
|
|
|
memcpy(k.bytes, defaultpsk, sizeof(defaultpsk));
|
|
|
|
k.length = sizeof(defaultpsk);
|
|
|
|
// Bump up the last byte of PSK as needed
|
|
|
|
uint8_t *last = k.bytes + sizeof(defaultpsk) - 1;
|
|
|
|
*last = *last + pskIndex - 1; // index of 1 means no change vs defaultPSK
|
|
|
|
}
|
|
|
|
} else if (k.length < 16) {
|
|
|
|
// Error! The user specified only the first few bits of an AES128 key. So by convention we just pad the rest of the
|
|
|
|
// key with zeros
|
|
|
|
DEBUG_MSG("Warning: User provided a too short AES128 key - padding\n");
|
|
|
|
k.length = 16;
|
|
|
|
} else if (k.length < 32 && k.length != 16) {
|
|
|
|
// Error! The user specified only the first few bits of an AES256 key. So by convention we just pad the rest of the
|
|
|
|
// key with zeros
|
|
|
|
DEBUG_MSG("Warning: User provided a too short AES256 key - padding\n");
|
|
|
|
k.length = 32;
|
2021-02-16 07:41:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-23 02:10:35 +00:00
|
|
|
return k;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Given a channel index, change to use the crypto key specified by that index
|
|
|
|
*/
|
|
|
|
int16_t Channels::setCrypto(ChannelIndex chIndex)
|
|
|
|
{
|
|
|
|
CryptoKey k = getKey(chIndex);
|
|
|
|
|
|
|
|
if (k.length < 0)
|
|
|
|
return -1;
|
|
|
|
else {
|
|
|
|
// Tell our crypto engine about the psk
|
|
|
|
crypto->setKey(k);
|
|
|
|
return getHash(chIndex);
|
|
|
|
}
|
2021-02-16 07:41:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Channels::initDefaults()
|
|
|
|
{
|
2021-02-17 05:06:23 +00:00
|
|
|
devicestate.channels_count = MAX_NUM_CHANNELS;
|
2021-02-16 07:41:52 +00:00
|
|
|
for (int i = 0; i < devicestate.channels_count; i++)
|
|
|
|
fixupChannel(i);
|
|
|
|
initDefaultChannel(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Channels::onConfigChanged()
|
|
|
|
{
|
|
|
|
// Make sure the phone hasn't mucked anything up
|
|
|
|
for (int i = 0; i < devicestate.channels_count; i++) {
|
2021-02-22 04:57:26 +00:00
|
|
|
Channel &ch = fixupChannel(i);
|
2021-02-16 07:41:52 +00:00
|
|
|
|
2021-02-22 03:16:38 +00:00
|
|
|
if (ch.role == Channel_Role_PRIMARY)
|
2021-02-16 07:41:52 +00:00
|
|
|
primaryIndex = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-22 03:16:38 +00:00
|
|
|
Channel &Channels::getByIndex(ChannelIndex chIndex)
|
2021-02-16 07:41:52 +00:00
|
|
|
{
|
|
|
|
assert(chIndex < devicestate.channels_count);
|
|
|
|
Channel *ch = devicestate.channels + chIndex;
|
|
|
|
return *ch;
|
|
|
|
}
|
|
|
|
|
2021-02-22 03:16:38 +00:00
|
|
|
void Channels::setChannel(const Channel &c)
|
|
|
|
{
|
|
|
|
Channel &old = getByIndex(c.index);
|
2021-02-16 07:41:52 +00:00
|
|
|
|
|
|
|
// if this is the new primary, demote any existing roles
|
2021-02-22 03:16:38 +00:00
|
|
|
if (c.role == Channel_Role_PRIMARY)
|
2021-02-23 02:45:03 +00:00
|
|
|
for (int i = 0; i < getNumChannels(); i++)
|
2021-02-22 03:16:38 +00:00
|
|
|
if (devicestate.channels[i].role == Channel_Role_PRIMARY)
|
2021-02-16 07:41:52 +00:00
|
|
|
devicestate.channels[i].role = Channel_Role_SECONDARY;
|
|
|
|
|
|
|
|
old = c; // slam in the new settings/role
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *Channels::getName(size_t chIndex)
|
|
|
|
{
|
|
|
|
// Convert the short "" representation for Default into a usable string
|
2021-02-22 03:16:38 +00:00
|
|
|
ChannelSettings &channelSettings = getByIndex(chIndex).settings;
|
2021-02-16 07:41:52 +00:00
|
|
|
const char *channelName = channelSettings.name;
|
|
|
|
if (!*channelName) { // emptystring
|
|
|
|
// Per mesh.proto spec, if bandwidth is specified we must ignore modemConfig enum, we assume that in that case
|
|
|
|
// the app fucked up and forgot to set channelSettings.name
|
|
|
|
|
|
|
|
if (channelSettings.bandwidth != 0)
|
|
|
|
channelName = "Unset";
|
|
|
|
else
|
|
|
|
switch (channelSettings.modem_config) {
|
|
|
|
case ChannelSettings_ModemConfig_Bw125Cr45Sf128:
|
|
|
|
channelName = "Medium";
|
|
|
|
break;
|
|
|
|
case ChannelSettings_ModemConfig_Bw500Cr45Sf128:
|
|
|
|
channelName = "ShortFast";
|
|
|
|
break;
|
|
|
|
case ChannelSettings_ModemConfig_Bw31_25Cr48Sf512:
|
|
|
|
channelName = "LongAlt";
|
|
|
|
break;
|
|
|
|
case ChannelSettings_ModemConfig_Bw125Cr48Sf4096:
|
|
|
|
channelName = "LongSlow";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
channelName = "Invalid";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return channelName;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate a short suffix used to disambiguate channels that might have the same "name" entered by the human but different PSKs.
|
|
|
|
* The ideas is that the PSK changing should be visible to the user so that they see they probably messed up and that's why they
|
|
|
|
their nodes
|
|
|
|
* aren't talking to each other.
|
|
|
|
*
|
|
|
|
* This string is of the form "#name-X".
|
|
|
|
*
|
|
|
|
* Where X is either:
|
|
|
|
* (for custom PSKS) a letter from A to Z (base26), and formed by xoring all the bytes of the PSK together,
|
|
|
|
*
|
|
|
|
* This function will also need to be implemented in GUI apps that talk to the radio.
|
|
|
|
*
|
|
|
|
* https://github.com/meshtastic/Meshtastic-device/issues/269
|
|
|
|
*/
|
|
|
|
const char *Channels::getPrimaryName()
|
|
|
|
{
|
|
|
|
static char buf[32];
|
|
|
|
|
|
|
|
char suffix;
|
2021-02-23 06:35:34 +00:00
|
|
|
// auto channelSettings = getPrimary();
|
2021-02-23 02:10:35 +00:00
|
|
|
// if (channelSettings.psk.size != 1) {
|
|
|
|
// We have a standard PSK, so generate a letter based hash.
|
|
|
|
uint8_t code = getHash(primaryIndex);
|
2021-02-16 07:41:52 +00:00
|
|
|
|
2021-02-23 02:10:35 +00:00
|
|
|
suffix = 'A' + (code % 26);
|
|
|
|
/* } else {
|
2021-02-16 07:41:52 +00:00
|
|
|
suffix = '0' + channelSettings.psk.bytes[0];
|
2021-02-23 02:10:35 +00:00
|
|
|
} */
|
2021-02-16 07:41:52 +00:00
|
|
|
|
2021-02-23 06:35:34 +00:00
|
|
|
snprintf(buf, sizeof(buf), "#%s-%c", getName(primaryIndex), suffix);
|
2021-02-16 07:41:52 +00:00
|
|
|
return buf;
|
2021-02-22 04:57:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Given a channel hash setup crypto for decoding that channel (or the primary channel if that channel is unsecured)
|
|
|
|
*
|
|
|
|
* This method is called before decoding inbound packets
|
|
|
|
*
|
2021-02-23 02:45:03 +00:00
|
|
|
* @return false if the channel hash or channel is invalid
|
2021-02-22 04:57:26 +00:00
|
|
|
*/
|
2021-02-23 02:45:03 +00:00
|
|
|
bool Channels::decryptForHash(ChannelIndex chIndex, ChannelHash channelHash)
|
2021-02-23 02:10:35 +00:00
|
|
|
{
|
2021-02-23 02:45:03 +00:00
|
|
|
if(chIndex > getNumChannels() || getHash(chIndex) != channelHash) {
|
|
|
|
DEBUG_MSG("Skipping channel %d due to invalid hash/index\n", chIndex);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
setCrypto(chIndex);
|
|
|
|
return true;
|
|
|
|
}
|
2021-02-23 02:10:35 +00:00
|
|
|
}
|
2021-02-22 04:57:26 +00:00
|
|
|
|
|
|
|
/** Given a channel index setup crypto for encoding that channel (or the primary channel if that channel is unsecured)
|
|
|
|
*
|
|
|
|
* This method is called before encoding outbound packets
|
|
|
|
*
|
|
|
|
* @eturn the (0 to 255) hash for that channel - if no suitable channel could be found, return -1
|
|
|
|
*/
|
2021-02-23 02:10:35 +00:00
|
|
|
int16_t Channels::setActiveByIndex(ChannelIndex channelIndex)
|
|
|
|
{
|
|
|
|
return setCrypto(channelIndex);
|
|
|
|
}
|