mirror of https://github.com/meshcore-dev/MeshCore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
545 lines
18 KiB
545 lines
18 KiB
#include "ClientACL.h"
|
|
#include <MeshCore.h>
|
|
#include <SHA256.h>
|
|
#include <ed_25519.h>
|
|
|
|
static File openWrite(FILESYSTEM* _fs, const char* filename) {
|
|
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
|
_fs->remove(filename);
|
|
return _fs->open(filename, FILE_O_WRITE);
|
|
#elif defined(RP2040_PLATFORM)
|
|
return _fs->open(filename, "w");
|
|
#else
|
|
return _fs->open(filename, "w", true);
|
|
#endif
|
|
}
|
|
|
|
void ClientACL::load(FILESYSTEM* fs, const mesh::LocalIdentity& self_id) {
|
|
_fs = fs;
|
|
num_clients = 0;
|
|
if (_fs->exists("/s_contacts")) {
|
|
#if defined(RP2040_PLATFORM)
|
|
File file = _fs->open("/s_contacts", "r");
|
|
#else
|
|
File file = _fs->open("/s_contacts");
|
|
#endif
|
|
if (file) {
|
|
bool full = false;
|
|
while (!full) {
|
|
ClientInfo c;
|
|
uint8_t pub_key[32];
|
|
uint8_t unused[2];
|
|
|
|
memset(&c, 0, sizeof(c));
|
|
|
|
bool success = (file.read(pub_key, 32) == 32);
|
|
success = success && (file.read((uint8_t *) &c.permissions, 1) == 1);
|
|
success = success && (file.read((uint8_t *) &c.extra.room.sync_since, 4) == 4);
|
|
success = success && (file.read(unused, 2) == 2);
|
|
success = success && (file.read((uint8_t *)&c.out_path_len, 1) == 1);
|
|
success = success && (file.read(c.out_path, 64) == 64);
|
|
success = success && (file.read(c.shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE); // will be recalculated below
|
|
|
|
if (!success) break; // EOF
|
|
|
|
c.id = mesh::Identity(pub_key);
|
|
self_id.calcSharedSecret(c.shared_secret, pub_key); // recalculate shared secrets in case our private key changed
|
|
if (num_clients < MAX_CLIENTS) {
|
|
clients[num_clients++] = c;
|
|
} else {
|
|
full = true;
|
|
}
|
|
}
|
|
file.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ClientACL::save(FILESYSTEM* fs, bool (*filter)(ClientInfo*)) {
|
|
_fs = fs;
|
|
File file = openWrite(_fs, "/s_contacts");
|
|
if (file) {
|
|
uint8_t unused[2];
|
|
memset(unused, 0, sizeof(unused));
|
|
|
|
for (int i = 0; i < num_clients; i++) {
|
|
auto c = &clients[i];
|
|
if (c->permissions == 0 || (filter && !filter(c))) continue; // skip deleted entries, or by filter function
|
|
|
|
bool success = (file.write(c->id.pub_key, 32) == 32);
|
|
success = success && (file.write((uint8_t *) &c->permissions, 1) == 1);
|
|
success = success && (file.write((uint8_t *) &c->extra.room.sync_since, 4) == 4);
|
|
success = success && (file.write(unused, 2) == 2);
|
|
success = success && (file.write((uint8_t *)&c->out_path_len, 1) == 1);
|
|
success = success && (file.write(c->out_path, 64) == 64);
|
|
success = success && (file.write(c->shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE);
|
|
|
|
if (!success) break; // write failed
|
|
}
|
|
file.close();
|
|
}
|
|
}
|
|
|
|
bool ClientACL::clear() {
|
|
if (!_fs) return false; // no filesystem, nothing to clear
|
|
if (_fs->exists("/s_contacts")) {
|
|
_fs->remove("/s_contacts");
|
|
}
|
|
memset(clients, 0, sizeof(clients));
|
|
num_clients = 0;
|
|
return true;
|
|
}
|
|
|
|
ClientInfo* ClientACL::getClient(const uint8_t* pubkey, int key_len) {
|
|
for (int i = 0; i < num_clients; i++) {
|
|
if (memcmp(pubkey, clients[i].id.pub_key, key_len) == 0) return &clients[i]; // already known
|
|
}
|
|
return NULL; // not found
|
|
}
|
|
|
|
ClientInfo* ClientACL::putClient(const mesh::Identity& id, uint8_t init_perms) {
|
|
uint32_t min_time = 0xFFFFFFFF;
|
|
ClientInfo* oldest = &clients[MAX_CLIENTS - 1];
|
|
for (int i = 0; i < num_clients; i++) {
|
|
if (id.matches(clients[i].id)) return &clients[i]; // already known
|
|
if (!clients[i].isAdmin() && clients[i].last_activity < min_time) {
|
|
oldest = &clients[i];
|
|
min_time = oldest->last_activity;
|
|
}
|
|
}
|
|
|
|
ClientInfo* c;
|
|
if (num_clients < MAX_CLIENTS) {
|
|
c = &clients[num_clients++];
|
|
} else {
|
|
c = oldest; // evict least active contact
|
|
}
|
|
int idx = c - clients;
|
|
memset(c, 0, sizeof(*c));
|
|
c->permissions = init_perms;
|
|
c->id = id;
|
|
c->out_path_len = OUT_PATH_UNKNOWN;
|
|
if (_rng) {
|
|
c->aead_nonce = (uint16_t)_rng->nextInt(NONCE_INITIAL_MIN, NONCE_INITIAL_MAX + 1);
|
|
}
|
|
nonce_at_last_persist[idx] = c->aead_nonce;
|
|
return c;
|
|
}
|
|
|
|
uint16_t ClientACL::nextAeadNonceFor(const ClientInfo& client) {
|
|
uint16_t nonce = client.nextAeadNonce();
|
|
if (nonce != 0) {
|
|
int idx = &client - clients;
|
|
if (idx >= 0 && idx < num_clients &&
|
|
(uint16_t)(client.aead_nonce - nonce_at_last_persist[idx]) >= NONCE_PERSIST_INTERVAL) {
|
|
nonce_dirty = true;
|
|
}
|
|
}
|
|
return nonce;
|
|
}
|
|
|
|
void ClientACL::loadNonces() {
|
|
if (!_fs) return;
|
|
#if defined(RP2040_PLATFORM)
|
|
File file = _fs->open("/s_nonces", "r");
|
|
#elif defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
|
File file = _fs->open("/s_nonces", FILE_O_READ);
|
|
#else
|
|
File file = _fs->open("/s_nonces", "r", false);
|
|
#endif
|
|
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);
|
|
for (int i = 0; i < num_clients; i++) {
|
|
if (memcmp(clients[i].id.pub_key, rec, 4) == 0) {
|
|
clients[i].aead_nonce = nonce;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
file.close();
|
|
}
|
|
}
|
|
|
|
void ClientACL::saveNonces() {
|
|
if (!_fs) return;
|
|
File file = openWrite(_fs, "/s_nonces");
|
|
if (file) {
|
|
for (int i = 0; i < num_clients; i++) {
|
|
file.write(clients[i].id.pub_key, 4);
|
|
file.write((uint8_t*)&clients[i].aead_nonce, 2);
|
|
nonce_at_last_persist[i] = clients[i].aead_nonce;
|
|
}
|
|
file.close();
|
|
nonce_dirty = false;
|
|
}
|
|
}
|
|
|
|
void ClientACL::finalizeNonceLoad(bool needs_bump) {
|
|
for (int i = 0; i < num_clients; i++) {
|
|
if (needs_bump) {
|
|
uint16_t old = clients[i].aead_nonce;
|
|
clients[i].aead_nonce += NONCE_BOOT_BUMP;
|
|
if (clients[i].aead_nonce == 0) clients[i].aead_nonce = 1;
|
|
if (clients[i].aead_nonce < old) {
|
|
MESH_DEBUG_PRINTLN("AEAD nonce wrapped after boot bump for client: %02x%02x%02x%02x",
|
|
clients[i].id.pub_key[0], clients[i].id.pub_key[1],
|
|
clients[i].id.pub_key[2], clients[i].id.pub_key[3]);
|
|
}
|
|
}
|
|
nonce_at_last_persist[i] = clients[i].aead_nonce;
|
|
}
|
|
nonce_dirty = false;
|
|
|
|
// Apply boot bump to session key nonces too
|
|
if (needs_bump) {
|
|
for (int i = 0; i < session_keys.getCount(); i++) {
|
|
auto entry = session_keys.getByIdx(i);
|
|
if (entry && (entry->state == SESSION_STATE_ACTIVE || entry->state == SESSION_STATE_DUAL_DECODE)) {
|
|
uint16_t old_nonce = entry->nonce;
|
|
entry->nonce += NONCE_BOOT_BUMP;
|
|
if (entry->nonce <= old_nonce) {
|
|
entry->nonce = 65535; // wrapped — force exhaustion so renegotiation happens
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ClientACL::applyPermissions(const mesh::LocalIdentity& self_id, const uint8_t* pubkey, int key_len, uint8_t perms) {
|
|
ClientInfo* c;
|
|
if ((perms & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) { // guest role is not persisted in contacts
|
|
c = getClient(pubkey, key_len);
|
|
if (c == NULL) return false; // partial pubkey not found
|
|
|
|
removeSessionKey(c->id.pub_key); // also remove session key if any
|
|
|
|
num_clients--; // delete from contacts[]
|
|
int i = c - clients;
|
|
while (i < num_clients) {
|
|
clients[i] = clients[i + 1];
|
|
nonce_at_last_persist[i] = nonce_at_last_persist[i + 1];
|
|
i++;
|
|
}
|
|
} else {
|
|
if (key_len < PUB_KEY_SIZE) return false; // need complete pubkey when adding/modifying
|
|
|
|
mesh::Identity id(pubkey);
|
|
c = putClient(id, 0);
|
|
|
|
c->permissions = perms; // update their permissions
|
|
self_id.calcSharedSecret(c->shared_secret, pubkey);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// --- Session key support (Phase 2) ---
|
|
|
|
int ClientACL::handleSessionKeyInit(const ClientInfo* client, const uint8_t* ephemeral_pub_A, uint8_t* reply_buf, mesh::RNG* rng) {
|
|
// 1. Generate ephemeral keypair B
|
|
uint8_t seed[SEED_SIZE];
|
|
rng->random(seed, SEED_SIZE);
|
|
uint8_t ephemeral_pub_B[PUB_KEY_SIZE];
|
|
uint8_t ephemeral_prv_B[PRV_KEY_SIZE];
|
|
ed25519_create_keypair(ephemeral_pub_B, ephemeral_prv_B, seed);
|
|
memset(seed, 0, SEED_SIZE);
|
|
|
|
// 2. Compute ephemeral_secret via X25519
|
|
uint8_t ephemeral_secret[PUB_KEY_SIZE];
|
|
ed25519_key_exchange(ephemeral_secret, ephemeral_pub_A, ephemeral_prv_B);
|
|
memset(ephemeral_prv_B, 0, PRV_KEY_SIZE);
|
|
|
|
// 3. Derive session_key = HMAC-SHA256(static_shared_secret, ephemeral_secret)
|
|
uint8_t new_session_key[SESSION_KEY_SIZE];
|
|
{
|
|
SHA256 sha;
|
|
sha.resetHMAC(client->shared_secret, PUB_KEY_SIZE);
|
|
sha.update(ephemeral_secret, PUB_KEY_SIZE);
|
|
sha.finalizeHMAC(client->shared_secret, PUB_KEY_SIZE, new_session_key, SESSION_KEY_SIZE);
|
|
}
|
|
memset(ephemeral_secret, 0, PUB_KEY_SIZE);
|
|
|
|
// 4. Store in pool (dual-decode: new key active, old key still valid)
|
|
auto entry = allocateSessionKey(client->id.pub_key);
|
|
if (!entry) return 0;
|
|
|
|
if (entry->state == SESSION_STATE_ACTIVE || entry->state == SESSION_STATE_DUAL_DECODE) {
|
|
memcpy(entry->prev_session_key, entry->session_key, SESSION_KEY_SIZE);
|
|
}
|
|
memcpy(entry->session_key, new_session_key, SESSION_KEY_SIZE);
|
|
entry->nonce = 1;
|
|
entry->state = SESSION_STATE_DUAL_DECODE;
|
|
entry->sends_since_last_recv = 0;
|
|
memset(new_session_key, 0, SESSION_KEY_SIZE);
|
|
|
|
// 5. Persist immediately
|
|
saveSessionKeys();
|
|
|
|
// 6. Write ephemeral_pub_B to reply
|
|
memcpy(reply_buf, ephemeral_pub_B, PUB_KEY_SIZE);
|
|
return PUB_KEY_SIZE;
|
|
}
|
|
|
|
const uint8_t* ClientACL::getSessionKey(const uint8_t* pub_key) {
|
|
auto entry = findSessionKey(pub_key);
|
|
if (entry && (entry->state == SESSION_STATE_ACTIVE || entry->state == SESSION_STATE_DUAL_DECODE)) {
|
|
return entry->session_key;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const uint8_t* ClientACL::getPrevSessionKey(const uint8_t* pub_key) {
|
|
auto entry = findSessionKey(pub_key);
|
|
if (entry && entry->state == SESSION_STATE_DUAL_DECODE) {
|
|
return entry->prev_session_key;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const uint8_t* ClientACL::getEncryptionKey(const ClientInfo& client) {
|
|
auto entry = findSessionKey(client.id.pub_key);
|
|
if (entry && (entry->state == SESSION_STATE_ACTIVE || entry->state == SESSION_STATE_DUAL_DECODE)
|
|
&& entry->sends_since_last_recv < SESSION_KEY_STALE_THRESHOLD
|
|
&& entry->nonce < 65535) {
|
|
return entry->session_key;
|
|
}
|
|
return client.shared_secret;
|
|
}
|
|
|
|
uint16_t ClientACL::getEncryptionNonce(const ClientInfo& client) {
|
|
auto entry = findSessionKey(client.id.pub_key);
|
|
if (entry && (entry->state == SESSION_STATE_ACTIVE || entry->state == SESSION_STATE_DUAL_DECODE)
|
|
&& entry->sends_since_last_recv < SESSION_KEY_STALE_THRESHOLD
|
|
&& entry->nonce < 65535) {
|
|
++entry->nonce;
|
|
if (entry->sends_since_last_recv < 255) entry->sends_since_last_recv++;
|
|
_session_keys_dirty = true;
|
|
return entry->nonce;
|
|
}
|
|
// Progressive fallback: keep incrementing counter even when not using session key
|
|
if (entry && entry->sends_since_last_recv < 255) {
|
|
entry->sends_since_last_recv++;
|
|
if (entry->sends_since_last_recv >= SESSION_KEY_ABANDON_THRESHOLD) {
|
|
int idx = &client - clients;
|
|
if (idx >= 0 && idx < num_clients)
|
|
clients[idx].flags &= ~CONTACT_FLAG_AEAD;
|
|
removeSessionKey(client.id.pub_key);
|
|
saveSessionKeys();
|
|
return 0; // ECB
|
|
}
|
|
if (entry->sends_since_last_recv >= SESSION_KEY_ECB_THRESHOLD) {
|
|
return 0; // ECB
|
|
}
|
|
}
|
|
return nextAeadNonceFor(client);
|
|
}
|
|
|
|
void ClientACL::onSessionConfirmed(const uint8_t* pub_key) {
|
|
auto entry = findSessionKey(pub_key);
|
|
if (entry) {
|
|
if (entry->state == SESSION_STATE_DUAL_DECODE) {
|
|
memset(entry->prev_session_key, 0, SESSION_KEY_SIZE);
|
|
entry->state = SESSION_STATE_ACTIVE;
|
|
saveSessionKeys();
|
|
}
|
|
entry->sends_since_last_recv = 0;
|
|
}
|
|
}
|
|
|
|
// --- Peer-index forwarding helpers ---
|
|
|
|
ClientInfo* ClientACL::resolveClient(int peer_idx, const int* matching_indexes) {
|
|
int i = matching_indexes[peer_idx];
|
|
if (i >= 0 && i < num_clients) return &clients[i];
|
|
return nullptr;
|
|
}
|
|
|
|
uint16_t ClientACL::peerNextAeadNonce(int peer_idx, const int* matching_indexes) {
|
|
auto* c = resolveClient(peer_idx, matching_indexes);
|
|
return c ? nextAeadNonceFor(*c) : 0;
|
|
}
|
|
|
|
const uint8_t* ClientACL::peerSessionKey(int peer_idx, const int* matching_indexes) {
|
|
auto* c = resolveClient(peer_idx, matching_indexes);
|
|
return c ? getSessionKey(c->id.pub_key) : nullptr;
|
|
}
|
|
|
|
const uint8_t* ClientACL::peerPrevSessionKey(int peer_idx, const int* matching_indexes) {
|
|
auto* c = resolveClient(peer_idx, matching_indexes);
|
|
return c ? getPrevSessionKey(c->id.pub_key) : nullptr;
|
|
}
|
|
|
|
void ClientACL::peerSessionKeyDecryptSuccess(int peer_idx, const int* matching_indexes) {
|
|
auto* c = resolveClient(peer_idx, matching_indexes);
|
|
if (c) onSessionConfirmed(c->id.pub_key);
|
|
}
|
|
|
|
const uint8_t* ClientACL::peerEncryptionKey(int peer_idx, const int* matching_indexes, const uint8_t* fallback) {
|
|
auto* c = resolveClient(peer_idx, matching_indexes);
|
|
return c ? getEncryptionKey(*c) : fallback;
|
|
}
|
|
|
|
uint16_t ClientACL::peerEncryptionNonce(int peer_idx, const int* matching_indexes) {
|
|
auto* c = resolveClient(peer_idx, matching_indexes);
|
|
return c ? getEncryptionNonce(*c) : 0;
|
|
}
|
|
|
|
// --- Flash-backed session key wrappers ---
|
|
|
|
static File openReadACL(FILESYSTEM* fs, const char* filename) {
|
|
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
|
return fs->open(filename, FILE_O_READ);
|
|
#elif defined(RP2040_PLATFORM)
|
|
return fs->open(filename, "r");
|
|
#else
|
|
return fs->open(filename, "r", false);
|
|
#endif
|
|
}
|
|
|
|
bool ClientACL::loadSessionKeyRecordFromFlash(const uint8_t* prefix,
|
|
uint8_t* flags, uint16_t* nonce, uint8_t* session_key, uint8_t* prev_session_key) {
|
|
if (!_fs) return false;
|
|
File f = openReadACL(_fs, "/s_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;
|
|
}
|
|
|
|
SessionKeyEntry* ClientACL::findSessionKey(const uint8_t* pub_key) {
|
|
auto entry = session_keys.findByPrefix(pub_key);
|
|
if (entry) return entry;
|
|
|
|
// Cache miss — try flash
|
|
uint8_t flags; uint16_t nonce;
|
|
uint8_t sk[SESSION_KEY_SIZE], psk[SESSION_KEY_SIZE];
|
|
if (!loadSessionKeyRecordFromFlash(pub_key, &flags, &nonce, sk, psk)) return nullptr;
|
|
|
|
// Save dirty evictee before overwriting
|
|
if (session_keys.isFull() && _session_keys_dirty) {
|
|
saveSessionKeys();
|
|
}
|
|
session_keys.applyLoaded(pub_key, flags, nonce, sk, psk);
|
|
return session_keys.findByPrefix(pub_key);
|
|
}
|
|
|
|
SessionKeyEntry* ClientACL::allocateSessionKey(const uint8_t* pub_key) {
|
|
auto entry = findSessionKey(pub_key);
|
|
if (entry) return entry;
|
|
|
|
// Not found anywhere — save dirty evictee before allocating
|
|
if (session_keys.isFull() && _session_keys_dirty) {
|
|
saveSessionKeys();
|
|
}
|
|
return session_keys.allocate(pub_key);
|
|
}
|
|
|
|
void ClientACL::removeSessionKey(const uint8_t* pub_key) {
|
|
session_keys.remove(pub_key);
|
|
}
|
|
|
|
void ClientACL::loadSessionKeys() {
|
|
if (!_fs) return;
|
|
File file = openReadACL(_fs, "/s_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);
|
|
}
|
|
session_keys.applyLoaded(rec, flags, nonce, &rec[7], prev_key);
|
|
}
|
|
file.close();
|
|
}
|
|
|
|
void ClientACL::saveSessionKeys() {
|
|
if (!_fs) return;
|
|
|
|
// 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 = openReadACL(_fs, "/s_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, "/s_sess_keys");
|
|
if (!wf) return;
|
|
|
|
// 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 (!session_keys.hasPrefix(rec) && !session_keys.isRemoved(rec)) {
|
|
wf.write(rec, rec_len);
|
|
}
|
|
pos += rec_len;
|
|
}
|
|
// Write current RAM entries (variable-length)
|
|
for (int i = 0; i < session_keys.getCount(); i++) {
|
|
uint8_t pub_key_prefix[4];
|
|
uint8_t flags;
|
|
uint16_t nonce;
|
|
uint8_t session_key[SESSION_KEY_SIZE];
|
|
uint8_t prev_session_key[SESSION_KEY_SIZE];
|
|
if (session_keys.getEntryForSave(i, pub_key_prefix, &flags, &nonce, session_key, prev_session_key)) {
|
|
wf.write(pub_key_prefix, 4);
|
|
wf.write(&flags, 1);
|
|
wf.write((uint8_t*)&nonce, 2);
|
|
wf.write(session_key, SESSION_KEY_SIZE);
|
|
if (flags & SESSION_FLAG_PREV_VALID) {
|
|
wf.write(prev_session_key, SESSION_KEY_SIZE);
|
|
}
|
|
}
|
|
}
|
|
wf.close();
|
|
_session_keys_dirty = false;
|
|
session_keys.clearRemoved();
|
|
}
|
|
|