package localdb import ( "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/sha256" "encoding/base64" "io" "os" "path/filepath" "testing" ) func TestEncryptCreatesPersistentKeyFile(t *testing.T) { stateDir := t.TempDir() t.Setenv("QFS_STATE_DIR", stateDir) t.Setenv("QUOTEFORGE_ENCRYPTION_KEY", "") ciphertext, err := Encrypt("secret-password") if err != nil { t.Fatalf("encrypt: %v", err) } if ciphertext == "" { t.Fatal("expected ciphertext") } keyPath := filepath.Join(stateDir, encryptionKeyFileName) info, err := os.Stat(keyPath) if err != nil { t.Fatalf("stat key file: %v", err) } if info.Mode().Perm() != 0600 { t.Fatalf("expected 0600 key file, got %v", info.Mode().Perm()) } } func TestDecryptMigratesLegacyCiphertext(t *testing.T) { stateDir := t.TempDir() t.Setenv("QFS_STATE_DIR", stateDir) t.Setenv("QUOTEFORGE_ENCRYPTION_KEY", "") legacyCiphertext := encryptWithKeyForTest(t, getLegacyEncryptionKey(), "legacy-password") plaintext, migrated, err := DecryptWithMetadata(legacyCiphertext) if err != nil { t.Fatalf("decrypt legacy: %v", err) } if plaintext != "legacy-password" { t.Fatalf("unexpected plaintext: %q", plaintext) } if !migrated { t.Fatal("expected legacy ciphertext to require migration") } currentCiphertext, err := Encrypt("legacy-password") if err != nil { t.Fatalf("encrypt current: %v", err) } plaintext, migrated, err = DecryptWithMetadata(currentCiphertext) if err != nil { t.Fatalf("decrypt current: %v", err) } if migrated { t.Fatal("did not expect current ciphertext to require migration") } } func encryptWithKeyForTest(t *testing.T, key []byte, plaintext string) string { t.Helper() block, err := aes.NewCipher(key) if err != nil { t.Fatalf("new cipher: %v", err) } gcm, err := cipher.NewGCM(block) if err != nil { t.Fatalf("new gcm: %v", err) } nonce := make([]byte, gcm.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { t.Fatalf("read nonce: %v", err) } ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil) return base64.StdEncoding.EncodeToString(ciphertext) } func TestLegacyEncryptionKeyRemainsDeterministic(t *testing.T) { hostname, _ := os.Hostname() expected := sha256.Sum256([]byte(hostname + "quoteforge-salt-2024")) actual := getLegacyEncryptionKey() if string(actual) != string(expected[:]) { t.Fatal("legacy key derivation changed") } }