Self-provision IFAC key from beacon network_name + passphrase

Firmware now derives the 64-byte IFAC key using the same algorithm
as Reticulum.py: SHA256(name) || SHA256(passphrase) → SHA256 →
HKDF(salt=IFAC_SALT). Called from ifac_init() when NVS has no key.

This eliminates the dependency on rnsd sending CMD_IFAC_KEY (which
Reticulum's RNodeInterface.py never does). The watch can now
self-provision IFAC after flash erase without any external tool.

Beacon network config: BEACON_NETWORK_NAME and BEACON_PASSPHRASE
defines in Beacon.h must match rnsd's RNodeInterface config.

Verified: watch shows "LoRa" (not "NO KEY"), IFAC flag set in
beacon packets.
This commit is contained in:
GlassOnTin 2026-04-02 17:41:56 +01:00
commit f11519a8d8
3 changed files with 44 additions and 1 deletions

View file

@ -35,6 +35,10 @@ const uint32_t beacon_interval_options[] = { 10000, 30000, 60000, 300000, 600000
#define BEACON_CR 5
#define BEACON_TXP 17
// Beacon IFAC network — must match rnsd's RNodeInterface config
#define BEACON_NETWORK_NAME "helv4net"
#define BEACON_PASSPHRASE "R3ticulum-priv8-m3sh"
// Pre-computed RNS destination hash for PLAIN destination "rnlog.beacon"
// Computed as: SHA256(SHA256("rnlog.beacon")[:10])[:16]
const uint8_t RNS_DEST_HASH[16] = {

View file

@ -21,6 +21,7 @@
#include "sodium/crypto_sign_ed25519.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "mbedtls/sha256.h"
// NVS namespace for IFAC key storage
#define IFAC_NVS_NAMESPACE "ifac"
@ -112,14 +113,49 @@ static int rns_hkdf_var(const uint8_t *ikm, size_t ikm_len,
return 0;
}
// ---- Reticulum IFAC_SALT (from Reticulum.py IFAC_SALT constant) ----
static const uint8_t RNS_IFAC_SALT[32] = {
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,
};
// ---- Self-provision IFAC key from network_name + passphrase ----
// Replicates Reticulum.py:821-826 IFAC key derivation:
// ifac_origin = SHA256(network_name) || SHA256(passphrase)
// ifac_origin_hash = SHA256(ifac_origin)
// ifac_key = HKDF(ikm=ifac_origin_hash, salt=IFAC_SALT, length=64)
static bool ifac_self_provision(const char *network_name, const char *passphrase) {
uint8_t name_hash[32], key_hash[32];
mbedtls_sha256((const uint8_t *)network_name, strlen(network_name), name_hash, 0);
mbedtls_sha256((const uint8_t *)passphrase, strlen(passphrase), key_hash, 0);
uint8_t ifac_origin[64];
memcpy(ifac_origin, name_hash, 32);
memcpy(ifac_origin + 32, key_hash, 32);
uint8_t origin_hash[32];
mbedtls_sha256(ifac_origin, 64, origin_hash, 0);
int ret = rns_hkdf_var(origin_hash, 32, RNS_IFAC_SALT, 32, ifac_key, 64);
if (ret != 0) return false;
ifac_nvs_save();
ifac_derive_keypair();
ifac_configured = true;
return true;
}
// ---- IFAC Initialization ----
// Call from setup() after lxmf_init_identity().
// Loads from NVS if available, otherwise self-provisions from beacon config.
static void ifac_init() {
if (ifac_nvs_load()) {
ifac_derive_keypair();
ifac_configured = true;
}
// If no key loaded, caller should call ifac_self_provision()
// with network_name/passphrase from beacon config.
}
// ---- Apply IFAC to Outgoing Packet ----

View file

@ -460,8 +460,11 @@ void setup() {
}
// Initialize LXMF identity (load from NVS or generate new)
lxmf_init_identity();
// Initialize IFAC authentication (load from NVS if provisioned)
// Initialize IFAC authentication (load from NVS, or self-provision)
ifac_init();
if (!ifac_configured) {
ifac_self_provision(BEACON_NETWORK_NAME, BEACON_PASSPHRASE);
}
// Load user settings from config EEPROM
uint8_t s_disp = EEPROM.read(config_addr(ADDR_CONF_DISP_TIMEOUT));
if (s_disp != 0xFF && s_disp >= 5 && s_disp <= 60)