Rename to RTNode-HeltecV4, replace 'boundary' with 'transport' in docs
- Rename project from RNodeTHV4 to RTNode-HeltecV4 - Update GitHub repo URL, firmware binary names (rtnode_heltec_v4.bin, rtnode_heltec_v3.bin) - Replace 'boundary node' with 'transport node' in README and flash.py descriptions - Update OLED title bar to 'RTNode' - Bump version to v1.0.18
This commit is contained in:
@@ -2,12 +2,61 @@
|
||||
|
||||
#include "Identity.h"
|
||||
#include "Transport.h"
|
||||
#include "Reticulum.h"
|
||||
#include "Cryptography/Hashes.h"
|
||||
#include "Cryptography/HKDF.h"
|
||||
|
||||
using namespace RNS;
|
||||
using namespace RNS::Type::Interface;
|
||||
|
||||
/*static*/ uint8_t Interface::DISCOVER_PATHS_FOR = MODE_ACCESS_POINT | MODE_GATEWAY;
|
||||
|
||||
void Interface::setup_ifac(const char* ifac_netname, const char* ifac_netkey) {
|
||||
assert(_impl);
|
||||
if (ifac_netname == nullptr && ifac_netkey == nullptr) {
|
||||
return;
|
||||
}
|
||||
// If both are empty strings, treat as no IFAC
|
||||
bool has_netname = (ifac_netname != nullptr && ifac_netname[0] != '\0');
|
||||
bool has_netkey = (ifac_netkey != nullptr && ifac_netkey[0] != '\0');
|
||||
if (!has_netname && !has_netkey) {
|
||||
return;
|
||||
}
|
||||
|
||||
TRACE("Interface::setup_ifac: setting up IFAC for " + _impl->_name);
|
||||
|
||||
// Build ifac_origin = SHA256(netname) || SHA256(netkey)
|
||||
Bytes ifac_origin;
|
||||
if (has_netname) {
|
||||
Bytes netname_bytes((const uint8_t*)ifac_netname, strlen(ifac_netname));
|
||||
Bytes hash = Identity::full_hash(netname_bytes);
|
||||
ifac_origin = ifac_origin + hash;
|
||||
}
|
||||
if (has_netkey) {
|
||||
Bytes netkey_bytes((const uint8_t*)ifac_netkey, strlen(ifac_netkey));
|
||||
Bytes hash = Identity::full_hash(netkey_bytes);
|
||||
ifac_origin = ifac_origin + hash;
|
||||
}
|
||||
|
||||
// Hash the combined origin
|
||||
Bytes ifac_origin_hash = Identity::full_hash(ifac_origin);
|
||||
|
||||
// Derive ifac_key via HKDF(salt=IFAC_SALT, ikm=ifac_origin_hash, length=64)
|
||||
Bytes salt(IFAC_SALT, IFAC_SALT_SIZE);
|
||||
_impl->_ifac_key = Cryptography::hkdf(64, ifac_origin_hash, salt);
|
||||
|
||||
// Create an identity from the derived key (64 bytes = 32 X25519 + 32 Ed25519)
|
||||
Identity ifac_id(false); // don't auto-generate keys
|
||||
ifac_id.load_private_key(_impl->_ifac_key);
|
||||
_impl->_ifac_id = ifac_id;
|
||||
|
||||
// Set _ifac_identity to non-empty to flag IFAC as enabled
|
||||
// (Transport checks this with operator bool)
|
||||
_impl->_ifac_identity = ifac_id.get_public_key();
|
||||
|
||||
TRACE("Interface::setup_ifac: IFAC configured, ifac_size=" + std::to_string(_impl->_ifac_size));
|
||||
}
|
||||
|
||||
void InterfaceImpl::handle_outgoing(const Bytes& data) {
|
||||
//TRACE("InterfaceImpl.handle_outgoing: data: " + data.toHex());
|
||||
TRACE("InterfaceImpl.handle_outgoing");
|
||||
|
||||
@@ -72,6 +72,9 @@ namespace RNS {
|
||||
size_t _txb = 0;
|
||||
bool _online = false;
|
||||
Bytes _ifac_identity;
|
||||
Bytes _ifac_key;
|
||||
Identity _ifac_id = {Type::NONE};
|
||||
uint8_t _ifac_size = 8; // DEFAULT_IFAC_SIZE for LoRa-type interfaces
|
||||
Type::Interface::modes _mode = Type::Interface::MODE_NONE;
|
||||
uint32_t _bitrate = 0;
|
||||
uint16_t _HW_MTU = 0;
|
||||
@@ -187,6 +190,11 @@ namespace RNS {
|
||||
inline bool online() const { assert(_impl); return _impl->_online; }
|
||||
inline std::string name() const { assert(_impl); return _impl->_name; }
|
||||
inline const Bytes& ifac_identity() const { assert(_impl); return _impl->_ifac_identity; }
|
||||
inline const Bytes& ifac_key() const { assert(_impl); return _impl->_ifac_key; }
|
||||
inline const Identity& ifac_id() const { assert(_impl); return _impl->_ifac_id; }
|
||||
inline uint8_t ifac_size() const { assert(_impl); return _impl->_ifac_size; }
|
||||
inline void ifac_size(uint8_t size) { assert(_impl); _impl->_ifac_size = size; }
|
||||
void setup_ifac(const char* ifac_netname, const char* ifac_netkey);
|
||||
inline Type::Interface::modes mode() const { assert(_impl); return _impl->_mode; }
|
||||
inline void mode(Type::Interface::modes mode) { assert(_impl); _impl->_mode = mode; }
|
||||
inline uint32_t bitrate() const { assert(_impl); return _impl->_bitrate; }
|
||||
|
||||
@@ -14,6 +14,15 @@
|
||||
|
||||
namespace RNS {
|
||||
|
||||
// IFAC salt used for key derivation (matches Python RNS Reticulum.IFAC_SALT)
|
||||
static const uint8_t IFAC_SALT[] = {
|
||||
0xad, 0xf5, 0x4d, 0x88, 0x2c, 0x9a, 0x9b, 0x80,
|
||||
0x77, 0x1e, 0xb4, 0x99, 0x5d, 0x70, 0x2d, 0x4a,
|
||||
0x3e, 0x73, 0x33, 0x91, 0xb2, 0xa0, 0xf5, 0x3f,
|
||||
0x41, 0x6d, 0x9f, 0x90, 0x7e, 0x55, 0xcf, 0xf8
|
||||
};
|
||||
static const size_t IFAC_SALT_SIZE = sizeof(IFAC_SALT);
|
||||
|
||||
class Reticulum {
|
||||
|
||||
public:
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "Interface.h"
|
||||
#include "Log.h"
|
||||
#include "Cryptography/Random.h"
|
||||
#include "Cryptography/HKDF.h"
|
||||
#include "Utilities/OS.h"
|
||||
#include "Utilities/Persistence.h"
|
||||
|
||||
@@ -734,43 +735,48 @@ static bool is_backbone_interface(const Interface& iface) {
|
||||
try {
|
||||
//if hasattr(interface, "ifac_identity") and interface.ifac_identity != None:
|
||||
if (interface.ifac_identity()) {
|
||||
// TODO
|
||||
/*p
|
||||
// Calculate packet access code
|
||||
ifac = interface.ifac_identity.sign(raw)[-interface.ifac_size:]
|
||||
// Calculate packet access code by signing the raw packet
|
||||
// and taking the last ifac_size bytes of the signature
|
||||
Bytes signature = interface.ifac_id().sign(raw);
|
||||
Bytes ifac = signature.right(interface.ifac_size());
|
||||
|
||||
// Generate mask
|
||||
mask = RNS.Cryptography.hkdf(
|
||||
length=len(raw)+interface.ifac_size,
|
||||
derive_from=ifac,
|
||||
salt=interface.ifac_key,
|
||||
context=None,
|
||||
)
|
||||
// Generate mask via HKDF
|
||||
Bytes mask = Cryptography::hkdf(
|
||||
raw.size() + interface.ifac_size(),
|
||||
ifac,
|
||||
interface.ifac_key()
|
||||
);
|
||||
|
||||
// Set IFAC flag
|
||||
new_header = bytes([raw[0] | 0x80, raw[1]])
|
||||
// Set IFAC flag in header byte 0
|
||||
uint8_t new_header0 = raw[0] | 0x80;
|
||||
uint8_t new_header1 = raw[1];
|
||||
|
||||
// Assemble new payload: new_header + ifac + raw[2:]
|
||||
Bytes new_raw;
|
||||
new_raw.append(new_header0);
|
||||
new_raw.append(new_header1);
|
||||
new_raw.append(ifac);
|
||||
new_raw.append(raw.mid(2));
|
||||
|
||||
// Assemble new payload with IFAC
|
||||
new_raw = new_header+ifac+raw[2:]
|
||||
|
||||
// Mask payload
|
||||
i = 0; masked_raw = b""
|
||||
for byte in new_raw:
|
||||
if i == 0:
|
||||
// Mask first header byte, but make sure the
|
||||
// IFAC flag is still set
|
||||
masked_raw += bytes([byte ^ mask[i] | 0x80])
|
||||
elif i == 1 or i > interface.ifac_size+1:
|
||||
Bytes masked_raw;
|
||||
for (size_t i = 0; i < new_raw.size(); i++) {
|
||||
if (i == 0) {
|
||||
// Mask first header byte, keep IFAC flag set
|
||||
masked_raw.append((uint8_t)((new_raw[i] ^ mask[i]) | 0x80));
|
||||
}
|
||||
else if (i == 1 || i > (size_t)(interface.ifac_size() + 1)) {
|
||||
// Mask second header byte and payload
|
||||
masked_raw += bytes([byte ^ mask[i]])
|
||||
else:
|
||||
masked_raw.append((uint8_t)(new_raw[i] ^ mask[i]));
|
||||
}
|
||||
else {
|
||||
// Don't mask the IFAC itself
|
||||
masked_raw += bytes([byte])
|
||||
i += 1
|
||||
masked_raw.append(new_raw[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Send it
|
||||
interface.on_outgoing(masked_raw)
|
||||
*/
|
||||
interface.send_outgoing(masked_raw);
|
||||
}
|
||||
else {
|
||||
interface.send_outgoing(raw);
|
||||
@@ -1258,8 +1264,8 @@ static bool is_backbone_interface(const Interface& iface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*static*/ void Transport::inbound(const Bytes& raw, const Interface& interface /*= {Type::NONE}*/) {
|
||||
TRACEF("Transport::inbound: received %d bytes", raw.size());
|
||||
/*static*/ void Transport::inbound(const Bytes& raw_in, const Interface& interface /*= {Type::NONE}*/) {
|
||||
TRACEF("Transport::inbound: received %d bytes", raw_in.size());
|
||||
++_packets_received;
|
||||
|
||||
// Heap telemetry: snapshot at entry
|
||||
@@ -1267,79 +1273,94 @@ static bool is_backbone_interface(const Interface& iface) {
|
||||
// CBA
|
||||
if (_callbacks._receive_packet) {
|
||||
try {
|
||||
_callbacks._receive_packet(raw, interface);
|
||||
_callbacks._receive_packet(raw_in, interface);
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
DEBUG("Error while executing receive packet callback. The contained exception was: " + std::string(e.what()));
|
||||
}
|
||||
}
|
||||
// TODO
|
||||
/*p
|
||||
|
||||
// Mutable copy of raw data for IFAC processing
|
||||
Bytes raw = raw_in;
|
||||
|
||||
// If interface access codes are enabled,
|
||||
// we must authenticate each packet.
|
||||
//if len(raw) > 2:
|
||||
if (raw.size() > 2) {
|
||||
if interface != None and hasattr(interface, "ifac_identity") and interface.ifac_identity != None:
|
||||
if (interface && interface.ifac_identity()) {
|
||||
// Check that IFAC flag is set
|
||||
if raw[0] & 0x80 == 0x80:
|
||||
if len(raw) > 2+interface.ifac_size:
|
||||
if ((raw[0] & 0x80) == 0x80) {
|
||||
if (raw.size() > (size_t)(2 + interface.ifac_size())) {
|
||||
// Extract IFAC
|
||||
ifac = raw[2:2+interface.ifac_size]
|
||||
Bytes ifac = raw.mid(2, interface.ifac_size());
|
||||
|
||||
// Generate mask
|
||||
mask = RNS.Cryptography.hkdf(
|
||||
length=len(raw),
|
||||
derive_from=ifac,
|
||||
salt=interface.ifac_key,
|
||||
context=None,
|
||||
)
|
||||
Bytes mask = Cryptography::hkdf(
|
||||
raw.size(),
|
||||
ifac,
|
||||
interface.ifac_key()
|
||||
);
|
||||
|
||||
// Unmask payload
|
||||
i = 0; unmasked_raw = b""
|
||||
for byte in raw:
|
||||
if i <= 1 or i > interface.ifac_size+1:
|
||||
Bytes unmasked_raw;
|
||||
for (size_t i = 0; i < raw.size(); i++) {
|
||||
if (i <= 1 || i > (size_t)(interface.ifac_size() + 1)) {
|
||||
// Unmask header bytes and payload
|
||||
unmasked_raw += bytes([byte ^ mask[i]])
|
||||
else:
|
||||
unmasked_raw.append((uint8_t)(raw[i] ^ mask[i]));
|
||||
}
|
||||
else {
|
||||
// Don't unmask IFAC itself
|
||||
unmasked_raw += bytes([byte])
|
||||
i += 1
|
||||
raw = unmasked_raw
|
||||
unmasked_raw.append(raw[i]);
|
||||
}
|
||||
}
|
||||
raw = unmasked_raw;
|
||||
|
||||
// Unset IFAC flag
|
||||
new_header = bytes([raw[0] & 0x7f, raw[1]])
|
||||
uint8_t new_header0 = raw[0] & 0x7F;
|
||||
uint8_t new_header1 = raw[1];
|
||||
|
||||
// Re-assemble packet
|
||||
new_raw = new_header+raw[2+interface.ifac_size:]
|
||||
// Re-assemble packet without IFAC bytes
|
||||
Bytes new_raw;
|
||||
new_raw.append(new_header0);
|
||||
new_raw.append(new_header1);
|
||||
new_raw.append(raw.mid(2 + interface.ifac_size()));
|
||||
|
||||
// Calculate expected IFAC
|
||||
expected_ifac = interface.ifac_identity.sign(new_raw)[-interface.ifac_size:]
|
||||
Bytes expected_signature = interface.ifac_id().sign(new_raw);
|
||||
Bytes expected_ifac = expected_signature.right(interface.ifac_size());
|
||||
|
||||
// Check it
|
||||
if ifac == expected_ifac:
|
||||
raw = new_raw
|
||||
else:
|
||||
return
|
||||
|
||||
else:
|
||||
return
|
||||
|
||||
else:
|
||||
// If the IFAC flag is not set, but should be,
|
||||
// drop the packet.
|
||||
return
|
||||
|
||||
else:
|
||||
if (ifac == expected_ifac) {
|
||||
raw = new_raw;
|
||||
}
|
||||
else {
|
||||
TRACE("Transport::inbound: IFAC authentication failed, dropping packet");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
TRACE("Transport::inbound: packet too short for IFAC, dropping");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If the IFAC flag is not set, but should be, drop the packet
|
||||
TRACE("Transport::inbound: IFAC required but flag not set, dropping packet");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If the interface does not have IFAC enabled,
|
||||
// check the received packet IFAC flag.
|
||||
if raw[0] & 0x80 == 0x80:
|
||||
if ((raw[0] & 0x80) == 0x80) {
|
||||
// If the flag is set, drop the packet
|
||||
return
|
||||
TRACE("Transport::inbound: IFAC flag set but interface has no IFAC, dropping packet");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
while (_jobs_running) {
|
||||
TRACE("Transport::inbound: sleeping...");
|
||||
|
||||
Reference in New Issue
Block a user