This commit is contained in:
liquidraver committed 2026-08-20 21:13:15 +02:00
1 parent 1263211099
commit 9fc3ab7f7e
6 files changed
+192 -10

No files matched your search

@@ -532,16 +532,40 @@ bool ZephyrDataStore::loadMainIdentity(mesh::LocalIdentity &identity)
{
uint8_t buf[PRV_KEY_SIZE + PUB_KEY_SIZE + 32];
size_t len = 0;
if (!openRead(MAIN_ID_FILE, buf, sizeof(buf), len) || len < PRV_KEY_SIZE + PUB_KEY_SIZE) {
if (!openRead(MAIN_ID_FILE, buf, sizeof(buf), len) || len < PRV_KEY_SIZE) {
return false;
}
return identity.readFrom(buf, len);
if (identity.readFromStorage(buf, len)) {
return true;
}
if (identity.recoverFromStorage(buf, len)) {
/* Deliberately not re-persisted: the file is the only record of what
* went wrong, and re-deriving costs a few ms per boot. The repeated
* warning is the point — this state should be visible, not papered
* over silently. */
LOG_WRN("main identity pub/prv mismatch - advertising the pub its private key owns");
return true;
}
/* No layout coheres — the stored pub is not prv·B under any reading, so
* the pair is unusable: adverts would verify nowhere, inbound DMs would
* not decrypt, and CMD_EXPORT_PRIVATE_KEY would hand the app a key that
* contradicts SELF_INFO. Returning false makes the caller generate a
* fresh identity, so park the bytes first rather than overwriting them —
* the private key may still be extractable by hand. */
LOG_ERR("main identity incoherent (%d bytes) - keeping bytes at %s, regenerating",
(int)len, MAIN_ID_BAD_FILE);
fs_unlink(MAIN_ID_BAD_FILE);
if (fs_rename(MAIN_ID_FILE, MAIN_ID_BAD_FILE) < 0) {
LOG_ERR("failed to park corrupt identity - regenerating over it");
}
return false;
}
bool ZephyrDataStore::saveMainIdentity(const mesh::LocalIdentity &identity)
{
uint8_t buf[PRV_KEY_SIZE + PUB_KEY_SIZE + 32];
size_t n = identity.writeTo(buf, sizeof(buf));
size_t n = identity.writeToStorage(buf, sizeof(buf));
if (n == 0) {
return false;
}
@@ -75,6 +75,9 @@ private:
static constexpr const char *MNT_POINT = "/lfs";
static constexpr const char *PREFS_FILE = "/lfs/new_prefs";
static constexpr const char *MAIN_ID_FILE = "/lfs/_main.id";
/* Where an identity that parses under no known layout is parked before
* a fresh one is generated over the top — see loadMainIdentity(). */
static constexpr const char *MAIN_ID_BAD_FILE = "/lfs/_main.id.bad";
static constexpr const char *SHUTDOWN_FILE = "/lfs/shutdn";
/* External QSPI flash (optional) - contacts, channels, blobs */
+21 -4
View File
@@ -69,13 +69,28 @@ bool RepeaterDataStore::loadIdentity(mesh::LocalIdentity& id) {
LOG_DBG("loadIdentity: read %d bytes from %s", (int)n, path);
if (n >= PRV_KEY_SIZE) {
if (id.readFrom(buf, n)) {
if (id.readFromStorage(buf, n)) {
LOG_INF("Loaded identity from %s", path);
return true;
}
LOG_ERR("loadIdentity: readFrom failed");
if (id.recoverFromStorage(buf, n)) {
/* Not re-persisted on purpose — see ZephyrDataStore::loadMainIdentity. */
LOG_WRN("identity pub/prv mismatch - advertising the pub its private key owns");
return true;
}
LOG_ERR("loadIdentity: no coherent key layout in %d bytes", (int)n);
}
/* Unusable pair — the caller regenerates, so park the bytes instead of
* letting a fresh identity overwrite them (same as the companion). */
char bad_path[56];
if (snprintf(bad_path, sizeof(bad_path), "%s.bad", path) < (int)sizeof(bad_path)) {
fs_unlink(bad_path);
if (fs_rename(path, bad_path) == 0) {
LOG_ERR("Identity file corrupt - kept at %s", bad_path);
return false;
}
}
LOG_ERR("Identity file corrupt");
return false;
}
@@ -101,8 +116,10 @@ bool RepeaterDataStore::saveIdentity(const mesh::LocalIdentity& id) {
return false;
}
uint8_t buf[PRV_KEY_SIZE];
int len = id.writeTo(buf, sizeof(buf));
/* pub || prv, same as the companion and Arduino MeshCore. Older builds
* wrote prv alone (64 bytes); readFromStorage() still accepts those. */
uint8_t buf[PUB_KEY_SIZE + PRV_KEY_SIZE];
int len = id.writeToStorage(buf, sizeof(buf));
ssize_t n = fs_write(&file, buf, len);
ret = fs_sync(&file);
fs_close(&file);
+38
View File
@@ -61,8 +61,46 @@ public:
void calcSharedSecret(uint8_t *secret, const uint8_t *other_pub_key) const;
static bool validatePrivateKey(const uint8_t prv[64]);
/* Arduino-compatible *buffer* format: prv || pub (or prv alone when the
* buffer is only PRV_KEY_SIZE). This is protocol-facing — it is what
* CMD_EXPORT/IMPORT_PRIVATE_KEY and the `prv.key` CLI put on the wire —
* so it must stay byte-identical to upstream. Do NOT use it for
* on-flash storage; use readFromStorage()/writeToStorage() instead. */
bool readFrom(const uint8_t *src, size_t len);
size_t writeTo(uint8_t *dest, size_t max_len) const;
/* On-flash format: pub || prv, matching Arduino MeshCore's IdentityStore
* (LocalIdentity::writeTo(Stream&)). Kept separate from the buffer
* format above because the two orders differ and conflating them is
* exactly how a node ends up advertising a key it cannot sign for. */
size_t writeToStorage(uint8_t *dest, size_t max_len) const;
/* Tolerant counterpart to writeToStorage(). Accepts every layout either
* project has ever written and tells them apart by deriving the pub from
* the candidate prv — the stored pub is a 256-bit checksum over the prv,
* so a wrong guess cannot match:
*
* len == 64 -> prv only (ZephCore repeater/room <= 1.17.x)
* len == 96 -> pub || prv (Arduino MeshCore, ZephCore >= 1.17.2)
* len == 96 -> prv || pub (ZephCore companion <= 1.17.x)
*
* pub_key is always the derived value, never the stored bytes, so a
* loaded identity is coherent by construction. Returns false when no
* layout coheres — the pair is unusable and the caller must not run
* with it. */
bool readFromStorage(const uint8_t *src, size_t len);
/* Last resort after readFromStorage() fails: one half of a 96-byte blob
* may still be an intact private key (the other having been clobbered by
* a stale/foreign write). Tests both candidate halves with
* validatePrivateKey() and adopts one only if exactly one qualifies —
* ambiguity means we cannot tell which half is which, and guessing wrong
* would cement a garbage identity. Recovers in RAM only: the file is
* left alone so the evidence survives and the warning repeats each boot.
* Note this changes the node's advertised pub_key to the one its private
* key actually owns; contacts must re-add it either way, since the pub it
* was advertising was never usable. */
bool recoverFromStorage(const uint8_t *src, size_t len);
};
} /* namespace mesh */
+59
View File
@@ -199,6 +199,65 @@ size_t LocalIdentity::writeTo(uint8_t *dest, size_t max_len) const
return PRV_KEY_SIZE + PUB_KEY_SIZE;
}
size_t LocalIdentity::writeToStorage(uint8_t *dest, size_t max_len) const
{
if (max_len < PUB_KEY_SIZE + PRV_KEY_SIZE) return 0;
memcpy(dest, pub_key, PUB_KEY_SIZE);
memcpy(dest + PUB_KEY_SIZE, prv_key, PRV_KEY_SIZE);
return PUB_KEY_SIZE + PRV_KEY_SIZE;
}
bool LocalIdentity::readFromStorage(const uint8_t *src, size_t len)
{
uint8_t derived[PUB_KEY_SIZE];
/* prv alone — nothing to disagree with, derive and go. */
if (len == PRV_KEY_SIZE) {
memcpy(prv_key, src, PRV_KEY_SIZE);
crypto_eddsa_scalarbase(pub_key, prv_key);
return true;
}
if (len != PUB_KEY_SIZE + PRV_KEY_SIZE) return false;
/* Canonical: pub || prv. */
crypto_eddsa_scalarbase(derived, src + PUB_KEY_SIZE);
if (memcmp(derived, src, PUB_KEY_SIZE) == 0) {
memcpy(prv_key, src + PUB_KEY_SIZE, PRV_KEY_SIZE);
memcpy(pub_key, derived, PUB_KEY_SIZE);
return true;
}
/* Legacy ZephCore companion: prv || pub. */
crypto_eddsa_scalarbase(derived, src);
if (memcmp(derived, src + PRV_KEY_SIZE, PUB_KEY_SIZE) == 0) {
memcpy(prv_key, src, PRV_KEY_SIZE);
memcpy(pub_key, derived, PUB_KEY_SIZE);
return true;
}
return false;
}
bool LocalIdentity::recoverFromStorage(const uint8_t *src, size_t len)
{
if (len != PUB_KEY_SIZE + PRV_KEY_SIZE) return false;
/* Where the prv would sit under each layout. */
const uint8_t *cand[2] = { src + PUB_KEY_SIZE, src }; /* pub||prv, prv||pub */
int found = -1;
for (int i = 0; i < 2; i++) {
if (!validatePrivateKey(cand[i])) continue;
if (found >= 0) return false; /* both plausible — refuse to guess */
found = i;
}
if (found < 0) return false;
memcpy(prv_key, cand[found], PRV_KEY_SIZE);
crypto_eddsa_scalarbase(pub_key, prv_key);
return true;
}
void LocalIdentity::sign(uint8_t *sig, const uint8_t *message, int msg_len) const
{
signExpanded(sig, prv_key, pub_key, message, (size_t)msg_len);