diff --git a/src/config/Config.h b/src/config/Config.h index c51246a..d76b5a5 100644 --- a/src/config/Config.h +++ b/src/config/Config.h @@ -50,6 +50,7 @@ #define SD_PATH_MESSAGES "/ratdeck/messages" #define SD_PATH_CONTACTS "/ratdeck/contacts" #define SD_PATH_IDENTITY "/ratdeck/identity/identity.key" +#define SD_PATH_IMPORT_ID "/ratdeck/identity/import.key" // --- TCP Client --- #define MAX_TCP_CONNECTIONS 4 diff --git a/src/reticulum/IdentityManager.cpp b/src/reticulum/IdentityManager.cpp index 8caa58a..90c4919 100644 --- a/src/reticulum/IdentityManager.cpp +++ b/src/reticulum/IdentityManager.cpp @@ -64,6 +64,44 @@ String IdentityManager::slotKeyPath(int slotNum) const { return String(path); } +int IdentityManager::importIdentity(const String& displayName) { + (void)displayName; + if (!_sd || !_sd->isReady()) return -1; + if ((int)_slots.size() >= MAX_IDENTITIES) return -1; + + uint8_t buffer[64]; + size_t bytesRead = 0; + if (!_sd->readFile(SD_PATH_IMPORT_ID, buffer, sizeof(buffer), bytesRead) || bytesRead != sizeof(buffer)) { + Serial.println("[IDMGR] Failed to read 64-byte import identity from SD"); + return -1; + } + + RNS::Bytes privKey(buffer, sizeof(buffer)); + RNS::Identity imported(false); + if (!imported.load_private_key(privKey)) { + Serial.println("[IDMGR] Import identity key failed validation"); + return -1; + } + + int slotNum = (int)_slots.size(); + String keyPath = slotKeyPath(slotNum); + if (!_flash->writeAtomic(keyPath.c_str(), privKey.data(), privKey.size())) { + Serial.println("[IDMGR] Failed to save imported identity key"); + return -1; + } + + IdentitySlot slot; + slot.hash = imported.hexhash(); + slot.displayName = ""; + slot.keyPath = keyPath; + slot.active = false; + _slots.push_back(slot); + + saveSlotMeta(); + Serial.printf("[IDMGR] Imported identity %d: %s\n", slotNum, slot.hash.substr(0, 16).c_str()); + return slotNum; +} + int IdentityManager::createIdentity(const String& displayName) { if ((int)_slots.size() >= MAX_IDENTITIES) return -1; diff --git a/src/reticulum/IdentityManager.h b/src/reticulum/IdentityManager.h index ecacf75..b22d557 100644 --- a/src/reticulum/IdentityManager.h +++ b/src/reticulum/IdentityManager.h @@ -25,6 +25,9 @@ public: // Current active identity index (-1 if none) int activeIndex() const { return _activeIdx; } + // Import identity from SD card, returns index + int importIdentity(const String& displayName = ""); + // Create a new identity, returns index int createIdentity(const String& displayName = ""); diff --git a/src/ui/screens/LvSettingsScreen.cpp b/src/ui/screens/LvSettingsScreen.cpp index dbe2844..ad2581c 100644 --- a/src/ui/screens/LvSettingsScreen.cpp +++ b/src/ui/screens/LvSettingsScreen.cpp @@ -463,6 +463,40 @@ void LvSettingsScreen::buildItems() { _items.push_back(newId); idx++; } + if (_sd && _sd->isReady()) { + SettingItem importId; + importId.label = "Import Identity"; + importId.type = SettingType::ACTION; + importId.formatter = [](int) { return String("[Enter]"); }; + importId.action = [this, &s]() { + if (!_sd || !_sd->isReady()) { + if (_ui) _ui->lvStatusBar().showToast("No SD card", 1200); + return; + } + if (!_sd->exists(SD_PATH_IMPORT_ID)) { + if (_ui) _ui->lvStatusBar().showToast("Missing import.key", 1200); + return; + } + if (!_idMgr) { + if (_ui) _ui->lvStatusBar().showToast("Not available", 1200); + return; + } + if (_idMgr->count() >= 8) { + if (_ui) _ui->lvStatusBar().showToast("Identity slots full", 1200); + return; + } + int idx = _idMgr->importIdentity(s.displayName); + if (idx >= 0) { + if (_ui) _ui->lvStatusBar().showToast("Identity imported", 1200); + buildItems(); + rebuildCategoryList(); + } else if (_ui) { + _ui->lvStatusBar().showToast("Identity import failed", 1200); + } + }; + _items.push_back(importId); + idx++; + } _items.push_back({"Auto Announce", SettingType::INTEGER, [&s]() { return s.announceInterval; }, [&s](int v) { s.announceInterval = v; }, [](int v) { return String(v) + "m"; }, 30, 60 * 6, 5}); // 30m - 6h