package secrets import ( "os" "path/filepath" "strings" "testing" ) func TestGenerateMasterKey(t *testing.T) { k1, err := GenerateMasterKey() if err != nil { t.Fatalf("GenerateMasterKey: %v", err) } if len(k1) != MasterKeyLen { t.Fatalf("key len = %d, want %d", len(k1), MasterKeyLen) } k2, err := GenerateMasterKey() if err != nil { t.Fatalf("GenerateMasterKey 2: %v", err) } if string(k1) == string(k2) { t.Fatalf("GenerateMasterKey returned identical bytes twice — crypto/rand not random?") } } func TestSaveLoadMasterKey_0600(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "master.key") key, err := GenerateMasterKey() if err != nil { t.Fatalf("GenerateMasterKey: %v", err) } if err := SaveMasterKey(path, key); err != nil { t.Fatalf("SaveMasterKey: %v", err) } info, err := os.Stat(path) if err != nil { t.Fatalf("stat: %v", err) } if info.Mode().Perm() != 0o600 { t.Fatalf("perm = %04o, want 0600", info.Mode().Perm()) } loaded, err := LoadMasterKey(path) if err != nil { t.Fatalf("LoadMasterKey: %v", err) } if string(loaded) != string(key) { t.Fatalf("loaded key differs from saved key") } } func TestLoadMasterKey_RefusesLoose(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "master.key") key, err := GenerateMasterKey() if err != nil { t.Fatalf("GenerateMasterKey: %v", err) } if err := SaveMasterKey(path, key); err != nil { t.Fatalf("SaveMasterKey: %v", err) } if err := os.Chmod(path, 0o644); err != nil { t.Fatalf("chmod 0644: %v", err) } _, err = LoadMasterKey(path) if err == nil { t.Fatalf("LoadMasterKey succeeded with mode 0644, want refuse") } if !strings.Contains(err.Error(), "REFUSE") { t.Fatalf("error missing REFUSE marker: %v", err) } } func TestLoadMasterKey_WrongLen(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "master.key") if err := os.WriteFile(path, []byte("tooshort"), 0o600); err != nil { t.Fatalf("write: %v", err) } _, err := LoadMasterKey(path) if err == nil { t.Fatalf("LoadMasterKey succeeded with wrong length, want error") } } func TestDeriveNamespaceKey_Deterministic(t *testing.T) { mk, _ := GenerateMasterKey() a1, err := DeriveNamespaceKey(mk, "nsA") if err != nil { t.Fatalf("DeriveA: %v", err) } a2, err := DeriveNamespaceKey(mk, "nsA") if err != nil { t.Fatalf("DeriveA 2: %v", err) } if string(a1) != string(a2) { t.Fatalf("same (master,namespace) yielded different sub-keys") } if len(a1) != SubKeyLen { t.Fatalf("sub-key len = %d, want %d", len(a1), SubKeyLen) } } func TestDeriveNamespaceKey_DifferentNamespaces(t *testing.T) { mk, _ := GenerateMasterKey() a, err := DeriveNamespaceKey(mk, "nsA") if err != nil { t.Fatalf("DeriveA: %v", err) } b, err := DeriveNamespaceKey(mk, "nsB") if err != nil { t.Fatalf("DeriveB: %v", err) } if string(a) == string(b) { t.Fatalf("different namespaces yielded the same sub-key") } } func TestDeriveNamespaceKey_EmptyNamespace(t *testing.T) { mk, _ := GenerateMasterKey() if _, err := DeriveNamespaceKey(mk, ""); err == nil { t.Fatalf("DeriveNamespaceKey(empty) succeeded, want error") } } func TestEncryptDecryptLine_RoundTrip(t *testing.T) { mk, _ := GenerateMasterKey() nsKey, err := DeriveNamespaceKey(mk, "ns") if err != nil { t.Fatalf("Derive: %v", err) } pt := []byte("API_KEY=supersecret") enc, err := EncryptLine(nsKey, 1, pt) if err != nil { t.Fatalf("EncryptLine: %v", err) } dec, err := DecryptLine(nsKey, 1, enc) if err != nil { t.Fatalf("DecryptLine: %v", err) } if string(dec) != string(pt) { t.Fatalf("round-trip mismatch: got %q want %q", dec, pt) } } func TestNonceUniqueness(t *testing.T) { mk, _ := GenerateMasterKey() nsKey, _ := DeriveNamespaceKey(mk, "ns") pt := []byte("same plaintext") a, _ := EncryptLine(nsKey, 1, pt) b, _ := EncryptLine(nsKey, 1, pt) if a == b { t.Fatalf("encrypting same plaintext twice yielded the same ciphertext — nonce not random") } } func TestAADLineNumber_SwapFails(t *testing.T) { mk, _ := GenerateMasterKey() nsKey, _ := DeriveNamespaceKey(mk, "ns") line1 := []byte("A=1") line2 := []byte("B=2") enc1, _ := EncryptLine(nsKey, 1, line1) enc2, _ := EncryptLine(nsKey, 2, line2) if _, err := DecryptLine(nsKey, 1, enc1); err != nil { t.Fatalf("decrypt line1 at pos1 failed: %v", err) } if _, err := DecryptLine(nsKey, 2, enc2); err != nil { t.Fatalf("decrypt line2 at pos2 failed: %v", err) } if _, err := DecryptLine(nsKey, 1, enc2); err == nil { t.Fatalf("decrypted line2 ciphertext at pos1 — AAD line-swap NOT detected") } if _, err := DecryptLine(nsKey, 2, enc1); err == nil { t.Fatalf("decrypted line1 ciphertext at pos2 — AAD line-swap NOT detected") } } func TestEncryptEnvFile_RoundTrip(t *testing.T) { mk, _ := GenerateMasterKey() nsKey, _ := DeriveNamespaceKey(mk, "ns") lines := []string{ "API_KEY=abc", "DB_PASSWORD=hunter2", "", "TOKEN=xyz", } enc, err := EncryptEnvFile(nsKey, lines) if err != nil { t.Fatalf("EncryptEnvFile: %v", err) } dec, err := DecryptEnvFile(nsKey, enc) if err != nil { t.Fatalf("DecryptEnvFile: %v", err) } if len(dec) != len(lines) { t.Fatalf("len(dec) = %d, want %d", len(dec), len(lines)) } for i, want := range lines { if dec[i] != want { t.Fatalf("line %d: got %q want %q", i, dec[i], want) } } } func TestGenerateLoadCredentialFile(t *testing.T) { mk, _ := GenerateMasterKey() nsKey, _ := DeriveNamespaceKey(mk, "ns") lines := []string{ "API_KEY=value1", "DB_PASSWORD=value2", } enc, err := EncryptEnvFile(nsKey, lines) if err != nil { t.Fatalf("EncryptEnvFile: %v", err) } encLines := strings.Split(strings.TrimRight(enc, "\n"), "\n") m, err := GenerateLoadCredentialFile(nsKey, encLines) if err != nil { t.Fatalf("GenerateLoadCredentialFile: %v", err) } if m["API_KEY"] != "value1" { t.Fatalf("API_KEY = %q, want value1", m["API_KEY"]) } if m["DB_PASSWORD"] != "value2" { t.Fatalf("DB_PASSWORD = %q, want value2", m["DB_PASSWORD"]) } if len(m) != 2 { t.Fatalf("map size = %d, want 2", len(m)) } }