package physical import ( "context" "encoding/json" "errors" "os" "path/filepath" "strings" "testing" "homekey.local/provisioner/drivers/sesame" ) func TestPrivatePersistenceAndPublicViews(t *testing.T) { dir := t.TempDir() target := sesame.Target{UUID: "00112233-4455-6677-8899-aabbccddeeff", Model: "sesame_6_pro", Adapter: "hci0", Registered: true} id, _ := identity(target) record := Record{id, "Front door", "sesame", target} secret := "000102030405060708090a0b0c0d0e0f" os.Mkdir(filepath.Join(dir, "physical-lock-keys"), 0700) if e := writeJSON(filepath.Join(dir, "physical-lock-keys", id+".json"), sesame.Credential{Secret: secret}); e != nil { t.Fatal(e) } if e := writeJSON(filepath.Join(dir, "physical-locks.json"), []Record{record}); e != nil { t.Fatal(e) } ctx, cancel := context.WithCancel(context.Background()) cancel() m, e := Open(ctx, dir, func(context.Context, sesame.Target) (sesame.Link, sesame.Target, error) { t.Error("dial after cancellation") return nil, sesame.Target{}, ctx.Err() }) if e != nil { t.Fatal(e) } defer m.Close() b, _ := json.Marshal(m.Views()) if strings.Contains(string(b), secret) || strings.Contains(string(b), "secret") { t.Fatal("public view leaks credentials") } if !m.Has(id) || m.Has("missing") { t.Fatal("registry lookup") } k, e := m.readKey(id) if e != nil || k.Secret != secret { t.Fatal("key not persisted") } for _, path := range []string{m.keyPath(id), filepath.Join(dir, "physical-locks.json")} { st, e := os.Stat(path) if e != nil || st.Mode().Perm() != 0600 { t.Fatal("unsafe file permissions", path) } } var records []Record b, _ = os.ReadFile(filepath.Join(dir, "physical-locks.json")) if e = json.Unmarshal(b, &records); e != nil || len(records) != 1 { t.Fatal("registry") } } func TestEnrollmentValidationBeforeBLE(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() m, e := Open(ctx, t.TempDir(), func(context.Context, sesame.Target) (sesame.Link, sesame.Target, error) { t.Fatal("invalid input reached Bluetooth") return nil, sesame.Target{}, nil }) if e != nil { t.Fatal(e) } defer m.Close() target := sesame.Target{UUID: "00112233-4455-6677-8899-aabbccddeeff", Model: "sesame_6_pro", Adapter: "hci0"} for _, in := range []Enrollment{{Name: "", Target: target, Mode: "register"}, {Name: "Door", Target: target, Mode: "reset"}, {Name: "Door", Target: target, Mode: "import"}, {Name: "Door", Target: sesame.Target{UUID: "../../secret", Model: "sesame_6_pro", Adapter: "hci0"}, Mode: "register"}} { if _, e = m.Add(ctx, in); e == nil { t.Fatal("accepted invalid enrollment") } } } func TestRegistryRejectsTraversalAndMissingKey(t *testing.T) { for _, id := range []string{"../../secret", "sesame-00112233-4455-6677-8899-aabbccddeeff"} { dir := t.TempDir() writeJSON(filepath.Join(dir, "physical-locks.json"), []Record{{ID: id, Name: "Door", Driver: "sesame", Target: sesame.Target{UUID: "00112233-4455-6677-8899-aabbccddeeff", Model: "sesame_6_pro", Adapter: "hci0"}}}) m, e := Open(context.Background(), dir, nil) if e == nil { m.Close() t.Fatal("invalid registry accepted") } } } // The encrypted login response is an independent AESCCM fixture for the // synthetic key/token in this test, not generated by the driver under test. type importLink struct { packets chan []byte done chan error sent bool } func newImportLink() *importLink { l := &importLink{packets: make(chan []byte, 4), done: make(chan error)} l.packets <- []byte{3, 8, 14, 0x11, 0x22, 0x33, 0x44} return l } func (l *importLink) Packets() <-chan []byte { return l.packets } func (l *importLink) Done() <-chan error { return l.done } func (l *importLink) Close() {} func (l *importLink) Write(ctx context.Context, b []byte) error { if !l.sent { l.sent = true l.packets <- []byte{5, 0xbe, 0x9d, 0x51, 0x39, 0x09, 0xb8, 0x0b, 0x9e, 0x70, 0x6a, 0x5e} } return nil } func TestImportPersistsAndRestoresWithoutPublicSecret(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() dir := t.TempDir() dial := func(_ context.Context, target sesame.Target) (sesame.Link, sesame.Target, error) { target.Registered = true return newImportLink(), target, nil } m, e := Open(ctx, dir, dial) if e != nil { t.Fatal(e) } var in Enrollment raw := `{"name":"Door","mode":"import","target":{"uuid":"00112233-4455-6677-8899-aabbccddeeff","model":"sesame_6_pro","adapter":"hci0"},"key":{"deviceUUID":"00112233-4455-6677-8899-aabbccddeeff","deviceModel":"sesame_6_pro","secretKey":"000102030405060708090a0b0c0d0e0f"}}` if e = json.Unmarshal([]byte(raw), &in); e != nil { t.Fatal(e) } record, e := m.Add(ctx, in) if e != nil { m.Close() t.Fatal(e) } if !m.Has(record.ID) { t.Fatal("import missing") } if _, e = m.Add(ctx, in); e == nil { t.Fatal("duplicate import") } public, _ := json.Marshal(m.Views()) if strings.Contains(string(public), in.Key.Secret) { t.Fatal("key leaked") } m.Close() m, e = Open(ctx, dir, dial) if e != nil { t.Fatal(e) } defer m.Close() if !m.Has(record.ID) { t.Fatal("import not restored") } } func removalFixture(t *testing.T) (*Manager, string, string) { t.Helper() dir := t.TempDir() ctx, cancel := context.WithCancel(context.Background()) cancel() target := sesame.Target{UUID: "00112233-4455-6677-8899-aabbccddeeff", Model: "sesame_6_pro", Adapter: "hci0", Registered: true} id, _ := identity(target) os.Mkdir(filepath.Join(dir, "physical-lock-keys"), 0700) writeJSON(filepath.Join(dir, "physical-lock-keys", id+".json"), sesame.Credential{Secret: "000102030405060708090a0b0c0d0e0f"}) writeJSON(filepath.Join(dir, "physical-locks.json"), []Record{{id, "Door", "sesame", target}}) m, e := Open(ctx, dir, nil) if e != nil { t.Fatal(e) } return m, id, dir } func TestRemovalDeletesKeyStopsWorkerAndPersists(t *testing.T) { m, id, dir := removalFixture(t) done := m.clientDone[id] if e := m.Remove(id); e != nil { t.Fatal(e) } select { case <-done: default: t.Fatal("worker still running") } if m.Has(id) || len(m.Views()) != 0 { t.Fatal("removed lock remains visible") } for _, p := range []string{m.keyPath(id), m.keyPath(id) + ".removing"} { if _, e := os.Stat(p); !os.IsNotExist(e) { t.Fatal("credential remains", p) } } if e := m.Remove(id); !errors.Is(e, ErrNotFound) { t.Fatal("unknown removal accepted", e) } m.Close() ctx, cancel := context.WithCancel(context.Background()) cancel() m, e := Open(ctx, dir, nil) if e != nil { t.Fatal(e) } defer m.Close() if m.Has(id) { t.Fatal("lock returned on restart") } } func TestFailedRemovalRestoresKey(t *testing.T) { m, id, dir := removalFixture(t) defer m.Close() registry := filepath.Join(dir, "physical-locks.json") os.Remove(registry) os.Mkdir(registry, 0700) if e := m.Remove(id); e == nil { t.Fatal("accepted failed registry update") } if !m.Has(id) { t.Fatal("lost active record") } if _, e := m.readKey(id); e != nil { t.Fatal("lost key after failed removal", e) } } func TestInterruptedRemovalRecoversOnOpen(t *testing.T) { m, id, dir := removalFixture(t) m.Close() key := m.keyPath(id) if e := os.Rename(key, key+".removing"); e != nil { t.Fatal(e) } ctx, cancel := context.WithCancel(context.Background()) cancel() m, e := Open(ctx, dir, nil) if e != nil { t.Fatal(e) } if !m.Has(id) { t.Fatal("precommit removal was not rolled back") } m.Close() os.Rename(key, key+".removing") writeJSON(filepath.Join(dir, "physical-locks.json"), []Record{}) m, e = Open(ctx, dir, nil) if e != nil { t.Fatal(e) } defer m.Close() if _, e = os.Stat(key + ".removing"); !os.IsNotExist(e) { t.Fatal("committed removal retained key") } }