feat: identity export/import (yaw-key-backup-1)

Portable encrypted identity backup compatible with the sister project's
format — argon2id KDF + nacl secretbox, peer ID visible in plaintext for
pre-import verification.

- crypto: ExportIdentity, ImportIdentity, SaveIdentity
- proto: export_identity / import_identity IPC commands + events
- ipc: export_identity returns encrypted blob; import_identity validates
  and decrypts (read-only — on-disk write requires daemon restart)
- netmgr: MasterIdentity() accessor
- daemon: --import-identity / --import-passphrase flags write identity.json
  and exit, enabling scripted account migration
- tests: roundtrip and wrong-passphrase rejection

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Fredrik Johansson
2026-06-22 21:44:48 +02:00
parent f1498697b6
commit b47f659b7d
7 changed files with 488 additions and 8 deletions

View File

@@ -60,6 +60,44 @@ func TestSignalingBoxTamperedFails(t *testing.T) {
}
}
func TestExportImportRoundtrip(t *testing.T) {
id := newTestIdentity(t)
passphrase := "correct horse battery staple"
blob, err := ExportIdentity(id, passphrase)
if err != nil {
t.Fatalf("ExportIdentity: %v", err)
}
// Exported blob must contain the public peer ID in plaintext.
if !strings.Contains(string(blob), string(id.PeerID())) {
t.Fatal("exported blob does not contain peer ID")
}
imported, err := ImportIdentity(blob, passphrase)
if err != nil {
t.Fatalf("ImportIdentity: %v", err)
}
if imported.PeerID() != id.PeerID() {
t.Fatalf("peer ID mismatch: got %s, want %s", imported.PeerID(), id.PeerID())
}
if imported.Alias != id.Alias {
t.Fatalf("alias mismatch: got %q, want %q", imported.Alias, id.Alias)
}
}
func TestImportWrongPassphraseFails(t *testing.T) {
id := newTestIdentity(t)
blob, err := ExportIdentity(id, "correct")
if err != nil {
t.Fatalf("ExportIdentity: %v", err)
}
if _, err := ImportIdentity(blob, "wrong"); err == nil {
t.Fatal("expected error with wrong passphrase, got nil")
}
}
// newTestIdentity creates a fresh in-memory identity for testing.
func newTestIdentity(t *testing.T) *Identity {
t.Helper()