internal/physical/manager_test.go
raw ยท 5184 bytes
package physical
import (
"context"
"encoding/json"
"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")
}
}