package store import ( "path/filepath" "testing" ) func k(b byte) []byte { key := make([]byte, 32) for i := range key { key[i] = b } return key } func tempStore(t *testing.T) *Store { t.Helper() st, err := Open(filepath.Join(t.TempDir(), "emcli.db")) if err != nil { t.Fatalf("Open: %v", err) } t.Cleanup(func() { st.Close() }) return st } func TestInitKeysThenUnlockBothSlotsRecoverSameDEK(t *testing.T) { admin, agent := k(0xAA), k(0xBB) st := tempStore(t) if err := st.InitKeys(admin, agent); err != nil { t.Fatalf("InitKeys: %v", err) } // Seal a password under the DEK that InitKeys set. if _, err := st.AddAccount(Account{ Name: "a", Mode: "RO", IMAPHost: "h", IMAPPort: 993, IMAPSecurity: "tls", AuthType: "password", Username: "u", Password: "pw", }); err != nil { t.Fatalf("AddAccount: %v", err) } // Re-open and unlock via the AGENT slot. path := st.dbPath() st.Close() st2, _ := Open(path) if err := st2.Unlock(RoleAgent, nil, agent); err != nil { t.Fatalf("Unlock(agent): %v", err) } got, err := st2.GetAccount("a") if err != nil || got.Password != "pw" { t.Fatalf("agent-slot decrypt: pw=%q err=%v", got.Password, err) } st2.Close() // Unlock via the ADMIN slot recovers the same DEK. st3, _ := Open(path) if err := st3.Unlock(RoleAdmin, admin, nil); err != nil { t.Fatalf("Unlock(admin): %v", err) } got3, err := st3.GetAccount("a") if err != nil || got3.Password != "pw" { t.Fatalf("admin-slot decrypt: pw=%q err=%v", got3.Password, err) } st3.Close() } func TestUnlockWrongKeyFails(t *testing.T) { st := tempStore(t) if err := st.InitKeys(k(0xAA), k(0xBB)); err != nil { t.Fatal(err) } path := st.dbPath() st.Close() st2, _ := Open(path) if err := st2.Unlock(RoleAdmin, k(0x11), nil); err == nil { t.Fatal("Unlock with wrong admin key must fail") } st2.Close() } func TestAdminSlotNotOpenableByAgentKey(t *testing.T) { st := tempStore(t) admin, agent := k(0xAA), k(0xBB) if err := st.InitKeys(admin, agent); err != nil { t.Fatal(err) } // RoleAdmin must use the admin slot; passing the agent key as the admin // key must fail — there is no fallback to the agent slot. if err := st.Unlock(RoleAdmin, agent, agent); err == nil { t.Fatal("agent key must not unlock the admin slot") } } func TestInitKeysIdempotentKeepsDEK(t *testing.T) { st := tempStore(t) admin, agent := k(0xAA), k(0xBB) if err := st.InitKeys(admin, agent); err != nil { t.Fatal(err) } st.AddAccount(Account{ Name: "a", Mode: "RO", IMAPHost: "h", IMAPPort: 993, IMAPSecurity: "tls", AuthType: "password", Username: "u", Password: "pw", }) // Second InitKeys must NOT regenerate the DEK (would orphan the password). if err := st.InitKeys(admin, agent); err != nil { t.Fatalf("re-InitKeys: %v", err) } got, err := st.GetAccount("a") if err != nil || got.Password != "pw" { t.Fatalf("password lost after re-init: pw=%q err=%v", got.Password, err) } }