Skip to content
Open
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
134 changes: 134 additions & 0 deletions examples/companion_radio/DataStore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,140 @@ void DataStore::saveChannels(DataStoreHost* host) {
}
}

void DataStore::loadNonces(DataStoreHost* host) {
File file = openRead(_getContactsChannelsFS(), "/nonces");
if (file) {
uint8_t rec[6]; // 4-byte pub_key prefix + 2-byte nonce
while (file.read(rec, 6) == 6) {
uint16_t nonce;
memcpy(&nonce, &rec[4], 2);
host->onNonceLoaded(rec, nonce);
}
file.close();
}
}

bool DataStore::saveNonces(DataStoreHost* host) {
File file = openWrite(_getContactsChannelsFS(), "/nonces");
if (file) {
int idx = 0;
uint8_t pub_key_prefix[4];
uint16_t nonce;
while (host->getNonceForSave(idx, pub_key_prefix, &nonce)) {
file.write(pub_key_prefix, 4);
file.write((uint8_t*)&nonce, 2);
idx++;
}
file.close();
return true;
}
return false;
}

void DataStore::loadSessionKeys(DataStoreHost* host) {
File file = openRead(_getContactsChannelsFS(), "/sess_keys");
if (!file) return;
while (true) {
uint8_t rec[SESSION_KEY_RECORD_MIN_SIZE];
if (file.read(rec, SESSION_KEY_RECORD_MIN_SIZE) != SESSION_KEY_RECORD_MIN_SIZE) break;
uint8_t flags = rec[4];
uint16_t nonce;
memcpy(&nonce, &rec[5], 2);
uint8_t prev_key[SESSION_KEY_SIZE];
if (flags & SESSION_FLAG_PREV_VALID) {
if (file.read(prev_key, SESSION_KEY_SIZE) != SESSION_KEY_SIZE) break;
} else {
memset(prev_key, 0, SESSION_KEY_SIZE);
}
host->onSessionKeyLoaded(rec, flags, nonce, &rec[7], prev_key);
}
file.close();
}

bool DataStore::saveSessionKeys(DataStoreHost* host) {
FILESYSTEM* fs = _getContactsChannelsFS();

// 1. Read old flash file into buffer (variable-length records)
uint8_t old_buf[MAX_SESSION_KEYS_FLASH * SESSION_KEY_RECORD_SIZE];
int old_len = 0;
File rf = openRead(fs, "/sess_keys");
if (rf) {
while (true) {
if (old_len + SESSION_KEY_RECORD_MIN_SIZE > (int)sizeof(old_buf)) break;
if (rf.read(&old_buf[old_len], SESSION_KEY_RECORD_MIN_SIZE) != SESSION_KEY_RECORD_MIN_SIZE) break;
uint8_t flags = old_buf[old_len + 4];
int rec_len = SESSION_KEY_RECORD_MIN_SIZE;
if (flags & SESSION_FLAG_PREV_VALID) {
if (old_len + SESSION_KEY_RECORD_SIZE > (int)sizeof(old_buf)) break;
if (rf.read(&old_buf[old_len + SESSION_KEY_RECORD_MIN_SIZE], SESSION_KEY_SIZE) != SESSION_KEY_SIZE) break;
rec_len = SESSION_KEY_RECORD_SIZE;
}
old_len += rec_len;
}
rf.close();
}

// 2. Write merged file
File wf = openWrite(fs, "/sess_keys");
if (!wf) return false;

// Write kept old records (variable-length)
int pos = 0;
while (pos + SESSION_KEY_RECORD_MIN_SIZE <= old_len) {
uint8_t* rec = &old_buf[pos];
uint8_t flags = rec[4];
int rec_len = (flags & SESSION_FLAG_PREV_VALID) ? SESSION_KEY_RECORD_SIZE : SESSION_KEY_RECORD_MIN_SIZE;
if (pos + rec_len > old_len) break;
if (!host->isSessionKeyInRAM(rec) && !host->isSessionKeyRemoved(rec)) {
wf.write(rec, rec_len);
}
pos += rec_len;
}
// Write current RAM entries (variable-length)
for (int idx = 0; idx < MAX_SESSION_KEYS_RAM; idx++) {
uint8_t pk[4]; uint8_t fl; uint16_t n; uint8_t sk[32]; uint8_t psk[32];
if (!host->getSessionKeyForSave(idx, pk, &fl, &n, sk, psk)) continue;
wf.write(pk, 4); wf.write(&fl, 1); wf.write((uint8_t*)&n, 2);
wf.write(sk, 32);
if (fl & SESSION_FLAG_PREV_VALID) {
wf.write(psk, 32);
}
}
wf.close();
return true;
}

bool DataStore::loadSessionKeyByPrefix(const uint8_t* prefix,
uint8_t* flags, uint16_t* nonce, uint8_t* session_key, uint8_t* prev_session_key) {
File f = openRead(_getContactsChannelsFS(), "/sess_keys");
if (!f) return false;
while (true) {
uint8_t rec[SESSION_KEY_RECORD_MIN_SIZE];
if (f.read(rec, SESSION_KEY_RECORD_MIN_SIZE) != SESSION_KEY_RECORD_MIN_SIZE) break;
uint8_t rec_flags = rec[4];
bool has_prev = (rec_flags & SESSION_FLAG_PREV_VALID);
if (memcmp(rec, prefix, 4) == 0) {
*flags = rec_flags;
memcpy(nonce, &rec[5], 2);
memcpy(session_key, &rec[7], SESSION_KEY_SIZE);
if (has_prev) {
if (f.read(prev_session_key, SESSION_KEY_SIZE) != SESSION_KEY_SIZE) break;
} else {
memset(prev_session_key, 0, SESSION_KEY_SIZE);
}
f.close();
return true;
}
// Skip prev_key if present
if (has_prev) {
uint8_t skip[SESSION_KEY_SIZE];
if (f.read(skip, SESSION_KEY_SIZE) != SESSION_KEY_SIZE) break;
}
}
f.close();
return false;
}

#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)

#define MAX_ADVERT_PKT_LEN (2 + 32 + PUB_KEY_SIZE + 4 + SIGNATURE_SIZE + MAX_ADVERT_DATA_SIZE)
Expand Down
14 changes: 14 additions & 0 deletions examples/companion_radio/DataStore.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ class DataStoreHost {
virtual bool getContactForSave(uint32_t idx, ContactInfo& contact) =0;
virtual bool onChannelLoaded(uint8_t channel_idx, const ChannelDetails& ch) =0;
virtual bool getChannelForSave(uint8_t channel_idx, ChannelDetails& ch) =0;
virtual bool onNonceLoaded(const uint8_t* pub_key_prefix, uint16_t nonce) { return false; }
virtual bool getNonceForSave(int idx, uint8_t* pub_key_prefix, uint16_t* nonce) { return false; }
virtual bool onSessionKeyLoaded(const uint8_t* pub_key_prefix, uint8_t flags, uint16_t nonce,
const uint8_t* session_key, const uint8_t* prev_session_key) { return false; }
virtual bool getSessionKeyForSave(int idx, uint8_t* pub_key_prefix, uint8_t* flags, uint16_t* nonce,
uint8_t* session_key, uint8_t* prev_session_key) { return false; }
virtual bool isSessionKeyInRAM(const uint8_t* pub_key_prefix) { return false; }
virtual bool isSessionKeyRemoved(const uint8_t* pub_key_prefix) { return false; }
};

class DataStore {
Expand Down Expand Up @@ -39,6 +47,12 @@ class DataStore {
void saveContacts(DataStoreHost* host);
void loadChannels(DataStoreHost* host);
void saveChannels(DataStoreHost* host);
void loadNonces(DataStoreHost* host);
bool saveNonces(DataStoreHost* host);
void loadSessionKeys(DataStoreHost* host);
bool saveSessionKeys(DataStoreHost* host);
bool loadSessionKeyByPrefix(const uint8_t* prefix,
uint8_t* flags, uint16_t* nonce, uint8_t* session_key, uint8_t* prev_session_key);
void migrateToSecondaryFS();
uint8_t getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]);
bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len);
Expand Down
47 changes: 47 additions & 0 deletions examples/companion_radio/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
next_ack_idx = 0;
sign_data = NULL;
dirty_contacts_expiry = 0;
next_nonce_persist = 0;
memset(advert_paths, 0, sizeof(advert_paths));
memset(send_scope.key, 0, sizeof(send_scope.key));

Expand Down Expand Up @@ -864,6 +865,16 @@ void MyMesh::begin(bool has_display) {
resetContacts();
_store->loadContacts(this);
bootstrapRTCfromContacts();

// Load persisted AEAD nonces and apply boot bump if needed
_store->loadNonces(this);
bool dirty_reset = wasDirtyReset(board);
finalizeNonceLoad(dirty_reset);
if (dirty_reset) saveNonces(); // persist bumped nonces immediately
next_nonce_persist = futureMillis(60000);

_store->loadSessionKeys(this);

addChannel("Public", PUBLIC_GROUP_PSK); // pre-configure Andy's public channel
_store->loadChannels(this);

Expand Down Expand Up @@ -1275,6 +1286,8 @@ void MyMesh::handleCmdFrame(size_t len) {
if (dirty_contacts_expiry) { // is there are pending dirty contacts write needed?
saveContacts();
}
if (isNonceDirty()) saveNonces();
saveSessionKeys();
board.reboot();
} else if (cmd_frame[0] == CMD_GET_BATT_AND_STORAGE) {
uint8_t reply[11];
Expand Down Expand Up @@ -1915,7 +1928,22 @@ void MyMesh::checkCLIRescueCmd() {

}

} else if (memcmp(cli_command, "rekey ", 6) == 0) {
const char* name_prefix = &cli_command[6];
ContactInfo* c = searchContactsByPrefix(name_prefix);
if (c) {
if (initiateSessionKeyNegotiation(*c)) {
Serial.print(" Session key negotiation started with: ");
Serial.println(c->name);
} else {
Serial.println(" Error: failed to initiate (no AEAD or pool full)");
}
} else {
Serial.println(" Error: contact not found");
}
} else if (strcmp(cli_command, "reboot") == 0) {
if (isNonceDirty()) saveNonces();
saveSessionKeys();
board.reboot(); // doesn't return
} else {
Serial.println(" Error: unknown command");
Expand Down Expand Up @@ -1952,6 +1980,14 @@ void MyMesh::checkSerialInterface() {
}
}

bool MyMesh::isSessionKeyInRAM(const uint8_t* pub_key_prefix) {
return isSessionKeyInRAMPool(pub_key_prefix);
}

bool MyMesh::isSessionKeyRemoved(const uint8_t* pub_key_prefix) {
return isSessionKeyRemovedFromPool(pub_key_prefix);
}

void MyMesh::loop() {
BaseChatMesh::loop();

Expand All @@ -1967,6 +2003,17 @@ void MyMesh::loop() {
dirty_contacts_expiry = 0;
}

// periodic AEAD nonce and session key persistence
if (next_nonce_persist && millisHasNowPassed(next_nonce_persist)) {
if (isNonceDirty()) {
saveNonces();
}
if (isSessionKeysDirty()) {
saveSessionKeys();
}
next_nonce_persist = futureMillis(60000);
}

#ifdef DISPLAY_CLASS
if (_ui) _ui->setHasConnection(_serial->isConnected());
#endif
Expand Down
23 changes: 23 additions & 0 deletions examples/companion_radio/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,18 @@ class MyMesh : public BaseChatMesh, public DataStoreHost {
bool getContactForSave(uint32_t idx, ContactInfo& contact) override { return getContactByIdx(idx, contact); }
bool onChannelLoaded(uint8_t channel_idx, const ChannelDetails& ch) override { return setChannel(channel_idx, ch); }
bool getChannelForSave(uint8_t channel_idx, ChannelDetails& ch) override { return getChannel(channel_idx, ch); }
bool onNonceLoaded(const uint8_t* pub_key_prefix, uint16_t nonce) override { return applyLoadedNonce(pub_key_prefix, nonce); }
bool getNonceForSave(int idx, uint8_t* pub_key_prefix, uint16_t* nonce) override { return getNonceEntry(idx, pub_key_prefix, nonce); }
bool onSessionKeyLoaded(const uint8_t* pub_key_prefix, uint8_t flags, uint16_t nonce,
const uint8_t* session_key, const uint8_t* prev_session_key) override {
return applyLoadedSessionKey(pub_key_prefix, flags, nonce, session_key, prev_session_key);
}
bool getSessionKeyForSave(int idx, uint8_t* pub_key_prefix, uint8_t* flags, uint16_t* nonce,
uint8_t* session_key, uint8_t* prev_session_key) override {
return getSessionKeyEntry(idx, pub_key_prefix, flags, nonce, session_key, prev_session_key);
}
bool isSessionKeyInRAM(const uint8_t* pub_key_prefix) override;
bool isSessionKeyRemoved(const uint8_t* pub_key_prefix) override;

void clearPendingReqs() {
pending_login = pending_status = pending_telemetry = pending_discovery = pending_req = 0;
Expand Down Expand Up @@ -180,6 +192,16 @@ class MyMesh : public BaseChatMesh, public DataStoreHost {
// helpers, short-cuts
void saveChannels() { _store->saveChannels(this); }
void saveContacts() { _store->saveContacts(this); }
void saveNonces() { if (_store->saveNonces(this)) clearNonceDirty(); }
void saveSessionKeys() { if (_store->saveSessionKeys(this)) { clearSessionKeysDirty(); clearSessionKeysRemoved(); } }
void onSessionKeysUpdated() override { saveSessionKeys(); }

// Flash-backed session key overrides
bool loadSessionKeyRecordFromFlash(const uint8_t* pub_key_prefix,
uint8_t* flags, uint16_t* nonce, uint8_t* session_key, uint8_t* prev_session_key) override {
return _store->loadSessionKeyByPrefix(pub_key_prefix, flags, nonce, session_key, prev_session_key);
}
void mergeAndSaveSessionKeys() override { saveSessionKeys(); }

DataStore* _store;
NodePrefs _prefs;
Expand All @@ -201,6 +223,7 @@ class MyMesh : public BaseChatMesh, public DataStoreHost {
uint8_t *sign_data;
uint32_t sign_data_len;
unsigned long dirty_contacts_expiry;
unsigned long next_nonce_persist;

TransportKey send_scope;

Expand Down
Loading