Skip to content
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

Implement public/private key encryption for direct messages #1509

Closed
wants to merge 4 commits into from
Closed
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
2 changes: 2 additions & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ lib_deps =
arduino-libraries/NTPClient@^3.1.0
lorol/LittleFS_esp32@^1.0.6
https://github.com/meshtastic/AXP202X_Library.git#8404abb6d4b486748636bc6ad72d2a47baaf5460
rweather/Crypto@^0.4.0

lib_ignore =
segger_rtt
Expand Down Expand Up @@ -153,6 +154,7 @@ lib_deps =
${arduino_base.lib_deps}
${environmental_base.lib_deps}
https://github.com/Kongduino/Adafruit_nRFCrypto.git
rweather/Crypto@^0.4.0

; Note: By default no lora device is created for this build - it uses a simulated interface
[env:nrf52840dk]
Expand Down
129 changes: 129 additions & 0 deletions src/mesh/CryptoEngine.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,134 @@
#include "configuration.h"
#include "CryptoEngine.h"
#include "NodeDB.h"
#include "RadioInterface.h"
#include <Crypto.h>
#include <Curve25519.h>
#include <BLAKE2b.h>

/**
* Create a public/private key pair with Curve25519.
*
* @param pubKey The destination for the public key.
* @param privKey The destination for the private key.
*/
void CryptoEngine::generateKeyPair(uint8_t *pubKey, uint8_t *privKey)
{
DEBUG_MSG("Generating Curve25519 key pair...\n");
Curve25519::dh1(public_key, private_key);
memcpy(pubKey, public_key, sizeof(public_key));
memcpy(privKey, private_key, sizeof(private_key));
}

void CryptoEngine::clearKeys()
{
memset(public_key, 0, sizeof(public_key));
memset(private_key, 0, sizeof(private_key));
}

/**
* Encrypt a packet's payload using a key generated with Curve25519 and Blake2b
* for a specific node.
*
* @param bytes is updated in place
*/
void CryptoEngine::encryptCurve25519_Blake2b(uint32_t toNode, uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes)
{
NodeInfo node = *nodeDB.getNode(toNode);
if (node.num < 1 || node.user.public_key[0] == 0) {
DEBUG_MSG("Node %d or their public_key not found\n", toNode);
return;
}

// Calculate the shared secret with the destination node and encrypt
crypto->setDHKey(toNode);
crypto->encrypt(fromNode, packetNum, numBytes, bytes);
}

/**
* Decrypt a packet's payload using a key generated with Curve25519 and Blake2b
* for a specific node.
*
* @param bytes is updated in place
*/
void CryptoEngine::decryptCurve25519_Blake2b(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes)
{
NodeInfo node = *nodeDB.getNode(fromNode);
if (node.num < 1 || node.user.public_key[0] == 0)
{
DEBUG_MSG("Node or its public key not found in database\n");
return;
}

// Calculate the shared secret with the sending node and decrypt
crypto->setDHKey(fromNode);
crypto->decrypt(fromNode, packetNum, numBytes, bytes);
}

/**
* Set the key used for encrypt, decrypt.
*
* As a special case: If all bytes are zero, we assume _no encryption_ and send all data in cleartext.
*
* @param nodeNum the node number of the node who's public key we want to use
*/
void CryptoEngine::setDHKey(uint32_t nodeNum)
{
NodeInfo node = *nodeDB.getNode(nodeNum);
if (node.num < 1 || node.user.public_key[0] == 0) {
DEBUG_MSG("Node %d or their public_key not found\n", nodeNum);
return;
}

// Calculate the shared secret with the specified node's
// public key and our private key
uint8_t *pubKey = node.user.public_key;
uint8_t shared_key[32];
uint8_t local_priv[32];
memcpy(shared_key, pubKey, 32);
memcpy(local_priv, private_key, 32);
Curve25519::dh2(shared_key, local_priv);

/**
* D.J. Bernstein reccomends hashing the shared key. We want to do this because there are
* at least 128 bits of entropy in the 256-bit output of the DH key exchange, but we don't
* really know where. If you extract, for instance, the first 128 bits with basic truncation,
* then you don't know if you got all of your 128 entropy bits, or less, possibly much less.
*
* No exploitable bias is really known at that point, but we know enough to be wary.
* Hashing the DH output is a simple and safe way to gather all the entropy and spread
* it around as needed.
*/
crypto->hash(shared_key, 32);

CryptoKey k;
memcpy(k.bytes, shared_key, 32);
k.length = 32;
crypto->setKey(k);
}


/**
* Hash arbitrary data using BLAKE2b.
*
* @param bytes
* @param numBytes
*/
void CryptoEngine::hash(uint8_t *bytes, size_t numBytes)
{
BLAKE2b hash;
size_t posn, len;
uint8_t size = numBytes;
uint8_t inc = 16;
hash.reset();
for (posn = 0; posn < size; posn += inc) {
len = size - posn;
if (len > inc)
len = inc;
hash.update(bytes + posn, len);
}
hash.finalize(bytes, 32);
}

void CryptoEngine::setKey(const CryptoKey &k)
{
Expand Down
13 changes: 13 additions & 0 deletions src/mesh/CryptoEngine.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include "mesh-pb-constants.h"
#include <Arduino.h>

struct CryptoKey {
Expand All @@ -24,9 +25,21 @@ class CryptoEngine

CryptoKey key = {};

uint8_t private_key[32];
bool keyPairSet;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please initialize private_key, public_key and keyPairSet

public:
uint8_t public_key[32];

virtual ~CryptoEngine() {}

virtual void generateKeyPair(uint8_t *pubKey, uint8_t *privKey);
virtual void clearKeys();
virtual void encryptCurve25519_Blake2b(uint32_t toNode, uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes);
virtual void decryptCurve25519_Blake2b(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes);
virtual void setDHKey(uint32_t nodeNum);
virtual void hash(uint8_t *bytes, size_t numBytes);

/**
* Set the key used for encrypt, decrypt.
*
Expand Down
17 changes: 15 additions & 2 deletions src/mesh/NodeDB.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,9 @@ void NodeDB::init()

// Include our owner in the node db under our nodenum
NodeInfo *info = getOrCreateNode(getNodeNum());

// Calculate Curve25519 public and private keys
crypto->generateKeyPair(owner.public_key, myNodeInfo.private_key);
info->user = owner;
info->has_user = true;

Expand Down Expand Up @@ -522,6 +525,14 @@ void NodeDB::updatePosition(uint32_t nodeId, const Position &p, RxSource src)
notifyObservers(true); // Force an update whether or not our node counts have changed
}

void printBytes(const uint8_t *bytes, size_t len)
{
for (size_t i = 0; i < len; i++) {
DEBUG_MSG("%02x", bytes[i]);
}
DEBUG_MSG("\n");
}

/** Update telemetry info for this node based on received metrics
* We only care about device telemetry here
*/
Expand Down Expand Up @@ -554,13 +565,15 @@ void NodeDB::updateUser(uint32_t nodeId, const User &p)
return;
}

DEBUG_MSG("old user %s/%s/%s\n", info->user.id, info->user.long_name, info->user.short_name);
DEBUG_MSG("old user %s/%s/%s/", info->user.id, info->user.long_name, info->user.short_name);
printBytes(info->user.public_key, 32);

bool changed = memcmp(&info->user, &p,
sizeof(info->user)); // Both of these blocks start as filled with zero so I think this is okay

info->user = p;
DEBUG_MSG("updating changed=%d user %s/%s/%s\n", changed, info->user.id, info->user.long_name, info->user.short_name);
DEBUG_MSG("updating changed=%d user %s/%s/%s/", changed, info->user.id, info->user.long_name, info->user.short_name);
printBytes(info->user.public_key, 32);
info->has_user = true;

if (changed) {
Expand Down
13 changes: 12 additions & 1 deletion src/mesh/Router.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ void printBytes(const char *label, const uint8_t *p, size_t numbytes)
DEBUG_MSG("\n");
}

bool isDirectMessage(const MeshPacket *p)
{
return p->to != -1 && (p->decoded.portnum == PortNum_ROUTING_APP) ? false : p->want_ack;
}

/**
* Send a packet on a suitable interface. This routine will
* later free() the packet to pool. This routine is not allowed to stall.
Expand Down Expand Up @@ -239,6 +244,7 @@ ErrorCode Router::send(MeshPacket *p)
#endif

auto encodeResult = perhapsEncode(p);
if (isDirectMessage(p)) p->want_ack = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think isDirectMessage(p) will never be true if p->want_ack is not true already before, so this line will not do anything.

if (encodeResult != Routing_Error_NONE) {
abortSendAndNak(encodeResult, p);
return encodeResult; // FIXME - this isn't a valid ErrorCode
Expand Down Expand Up @@ -304,6 +310,8 @@ bool perhapsDecode(MeshPacket *p)
DEBUG_MSG("Invalid portnum (bad psk?)!\n");
} else {
// parsing was successful
if (isDirectMessage(p) && p->to == nodeDB.getNodeNum()) // This is a direct message to us so decrypt the payload
crypto->decryptCurve25519_Blake2b(p->from, p->id, p->decoded.payload.size, p->decoded.payload.bytes);
p->which_payloadVariant = MeshPacket_decoded_tag; // change type to decoded
p->channel = chIndex; // change to store the index instead of the hash

Expand Down Expand Up @@ -344,12 +352,15 @@ bool perhapsDecode(MeshPacket *p)
return false;
}

/** Return 0 for success or a Routing_Errror code for failure
/** Return 0 for success or a Routing_Error code for failure
*/
Routing_Error perhapsEncode(MeshPacket *p)
{
// If the packet is not yet encrypted, do so now
if (p->which_payloadVariant == MeshPacket_decoded_tag) {
if (isDirectMessage(p)) // Encrypt the payload for the recipient node seperately from the rest of the packet
crypto->encryptCurve25519_Blake2b(p->to, getFrom(p), p->id, p->decoded.payload.size, p->decoded.payload.bytes);

static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union

size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), Data_fields, &p->decoded);
Expand Down
2 changes: 1 addition & 1 deletion src/mesh/generated/deviceonly.pb.h
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ extern const pb_msgdesc_t OEMStore_msg;

/* Maximum encoded size of messages (where known) */
#define ChannelFile_size 624
#define DeviceState_size 23728
#define DeviceState_size 26598
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ouch ... RAM usage...

#define OEMStore_size 2106

#ifdef __cplusplus
Expand Down
2 changes: 1 addition & 1 deletion src/mesh/generated/mesh.pb.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ PB_BIND(Location, Location, AUTO)
PB_BIND(MeshPacket, MeshPacket, 2)


PB_BIND(NodeInfo, NodeInfo, AUTO)
PB_BIND(NodeInfo, NodeInfo, 2)


PB_BIND(MyNodeInfo, MyNodeInfo, AUTO)
Expand Down
28 changes: 19 additions & 9 deletions src/mesh/generated/mesh.pb.h
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,9 @@ typedef struct _MyNodeInfo {
float channel_utilization;
/* Percent of airtime for transmission used within the last hour. */
float air_util_tx;
/* The private key of the device.
This is used to create a shared key with a remote device. */
pb_byte_t private_key[32];
} MyNodeInfo;

/* a gps position */
Expand Down Expand Up @@ -461,6 +464,9 @@ typedef struct _User {
Zero = not applicable (mobile or omni) or not specified
(use a value of 360 to indicate an antenna azimuth of zero degrees) */
uint32_t ant_azimuth;
/* The public key of the user's device.
This is sent out to other nodes on the mesh to allow them to compute a shared secret key. */
pb_byte_t public_key[32];
} User;

typedef PB_BYTES_ARRAY_T(237) Data_payload_t;
Expand Down Expand Up @@ -695,28 +701,28 @@ extern "C" {

/* Initializer values for message structs */
#define Position_init_default {0, 0, 0, 0, _Position_LocSource_MIN, _Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define User_init_default {"", "", "", {0}, _HardwareModel_MIN, 0, 0, 0, 0}
#define User_init_default {"", "", "", {0}, _HardwareModel_MIN, 0, 0, 0, 0, {0}}
#define RouteDiscovery_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}}
#define Routing_init_default {0, {RouteDiscovery_init_default}}
#define Data_init_default {_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, Location_init_default}
#define Location_init_default {0, 0, 0, 0, 0}
#define MeshPacket_init_default {0, 0, 0, 0, {Data_init_default}, 0, 0, 0, 0, 0, _MeshPacket_Priority_MIN, 0, _MeshPacket_Delayed_MIN}
#define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0, false, DeviceMetrics_init_default}
#define MyNodeInfo_init_default {0, 0, "", _CriticalErrorCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, 0, 0}
#define MyNodeInfo_init_default {0, 0, "", _CriticalErrorCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, 0, 0, {0}}
#define LogRecord_init_default {"", 0, "", _LogRecord_Level_MIN}
#define FromRadio_init_default {0, 0, {MyNodeInfo_init_default}}
#define ToRadio_init_default {0, {MeshPacket_init_default}}
#define ToRadio_PeerInfo_init_default {0, 0}
#define Compressed_init_default {_PortNum_MIN, {0, {0}}}
#define Position_init_zero {0, 0, 0, 0, _Position_LocSource_MIN, _Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define User_init_zero {"", "", "", {0}, _HardwareModel_MIN, 0, 0, 0, 0}
#define User_init_zero {"", "", "", {0}, _HardwareModel_MIN, 0, 0, 0, 0, {0}}
#define RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}}
#define Routing_init_zero {0, {RouteDiscovery_init_zero}}
#define Data_init_zero {_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, Location_init_zero}
#define Location_init_zero {0, 0, 0, 0, 0}
#define MeshPacket_init_zero {0, 0, 0, 0, {Data_init_zero}, 0, 0, 0, 0, 0, _MeshPacket_Priority_MIN, 0, _MeshPacket_Delayed_MIN}
#define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0, false, DeviceMetrics_init_zero}
#define MyNodeInfo_init_zero {0, 0, "", _CriticalErrorCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, 0, 0}
#define MyNodeInfo_init_zero {0, 0, "", _CriticalErrorCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, {0, 0, 0, 0, 0, 0, 0, 0}, 0, 0, 0, {0}}
#define LogRecord_init_zero {"", 0, "", _LogRecord_Level_MIN}
#define FromRadio_init_zero {0, 0, {MyNodeInfo_init_zero}}
#define ToRadio_init_zero {0, {MeshPacket_init_zero}}
Expand Down Expand Up @@ -751,6 +757,7 @@ extern "C" {
#define MyNodeInfo_has_wifi_tag 18
#define MyNodeInfo_channel_utilization_tag 19
#define MyNodeInfo_air_util_tx_tag 20
#define MyNodeInfo_private_key_tag 21
#define Position_latitude_i_tag 1
#define Position_longitude_i_tag 2
#define Position_altitude_tag 3
Expand Down Expand Up @@ -785,6 +792,7 @@ extern "C" {
#define User_tx_power_dbm_tag 10
#define User_ant_gain_dbi_tag 11
#define User_ant_azimuth_tag 12
#define User_public_key_tag 13
#define Data_portnum_tag 1
#define Data_payload_tag 2
#define Data_want_response_tag 3
Expand Down Expand Up @@ -864,7 +872,8 @@ X(a, STATIC, SINGULAR, UENUM, hw_model, 6) \
X(a, STATIC, SINGULAR, BOOL, is_licensed, 7) \
X(a, STATIC, SINGULAR, UINT32, tx_power_dbm, 10) \
X(a, STATIC, SINGULAR, UINT32, ant_gain_dbi, 11) \
X(a, STATIC, SINGULAR, UINT32, ant_azimuth, 12)
X(a, STATIC, SINGULAR, UINT32, ant_azimuth, 12) \
X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, public_key, 13)
#define User_CALLBACK NULL
#define User_DEFAULT NULL

Expand Down Expand Up @@ -952,7 +961,8 @@ X(a, STATIC, REPEATED, UINT32, air_period_tx, 16) \
X(a, STATIC, REPEATED, UINT32, air_period_rx, 17) \
X(a, STATIC, SINGULAR, BOOL, has_wifi, 18) \
X(a, STATIC, SINGULAR, FLOAT, channel_utilization, 19) \
X(a, STATIC, SINGULAR, FLOAT, air_util_tx, 20)
X(a, STATIC, SINGULAR, FLOAT, air_util_tx, 20) \
X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, private_key, 21)
#define MyNodeInfo_CALLBACK NULL
#define MyNodeInfo_DEFAULT NULL

Expand Down Expand Up @@ -1039,14 +1049,14 @@ extern const pb_msgdesc_t Compressed_msg;
#define Location_size 24
#define LogRecord_size 81
#define MeshPacket_size 347
#define MyNodeInfo_size 197
#define NodeInfo_size 281
#define MyNodeInfo_size 232
#define NodeInfo_size 316
#define Position_size 142
#define RouteDiscovery_size 40
#define Routing_size 42
#define ToRadio_PeerInfo_size 8
#define ToRadio_size 350
#define User_size 95
#define User_size 129

#ifdef __cplusplus
} /* extern "C" */
Expand Down