mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 22:11:38 +00:00
995b72df05
Replace static env-var-based issuer wiring with GUI-driven dynamic configuration stored encrypted in PostgreSQL. Operators can now configure, test, enable/disable, and manage issuers from the dashboard without restarting the server. Key changes: - AES-256-GCM encryption for sensitive issuer config at rest (PBKDF2 key derivation with 100k iterations) - Dynamic IssuerRegistry with sync.RWMutex replacing static map - Connector factory pattern (issuerfactory.NewFromConfig) replacing 140 lines of static wiring in main.go - Migration 000009: encrypted_config, last_tested_at, test_status, source columns on issuers table - Env var seeding on first boot with ON CONFLICT DO NOTHING - Registry Rebuild() for atomic map swap after CRUD operations - Issuer type validation against domain constants on Create - Audit trail for test connection results - Conditional seeding for step-ca/OpenSSL (only when env vars set) - GUI: source badge, connection test status on issuer detail page Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
189 lines
4.6 KiB
Go
189 lines
4.6 KiB
Go
package crypto
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
)
|
|
|
|
func TestEncryptDecryptRoundTrip(t *testing.T) {
|
|
key := DeriveKey("test-passphrase")
|
|
plaintext := []byte(`{"api_key":"secret123","org_id":"456"}`)
|
|
|
|
encrypted, err := Encrypt(plaintext, key)
|
|
if err != nil {
|
|
t.Fatalf("Encrypt failed: %v", err)
|
|
}
|
|
|
|
if bytes.Equal(encrypted, plaintext) {
|
|
t.Fatal("encrypted data should differ from plaintext")
|
|
}
|
|
|
|
decrypted, err := Decrypt(encrypted, key)
|
|
if err != nil {
|
|
t.Fatalf("Decrypt failed: %v", err)
|
|
}
|
|
|
|
if !bytes.Equal(decrypted, plaintext) {
|
|
t.Fatalf("round-trip failed: got %q, want %q", decrypted, plaintext)
|
|
}
|
|
}
|
|
|
|
func TestDecryptWrongKey(t *testing.T) {
|
|
key1 := DeriveKey("key-one")
|
|
key2 := DeriveKey("key-two")
|
|
plaintext := []byte("sensitive config data")
|
|
|
|
encrypted, err := Encrypt(plaintext, key1)
|
|
if err != nil {
|
|
t.Fatalf("Encrypt failed: %v", err)
|
|
}
|
|
|
|
_, err = Decrypt(encrypted, key2)
|
|
if err == nil {
|
|
t.Fatal("expected error when decrypting with wrong key")
|
|
}
|
|
}
|
|
|
|
func TestDecryptTamperedCiphertext(t *testing.T) {
|
|
key := DeriveKey("test-key")
|
|
plaintext := []byte("important data")
|
|
|
|
encrypted, err := Encrypt(plaintext, key)
|
|
if err != nil {
|
|
t.Fatalf("Encrypt failed: %v", err)
|
|
}
|
|
|
|
// Tamper with the ciphertext (flip a byte after the nonce)
|
|
if len(encrypted) > 13 {
|
|
encrypted[13] ^= 0xFF
|
|
}
|
|
|
|
_, err = Decrypt(encrypted, key)
|
|
if err == nil {
|
|
t.Fatal("expected error when decrypting tampered ciphertext")
|
|
}
|
|
}
|
|
|
|
func TestEncryptEmptyPlaintext(t *testing.T) {
|
|
key := DeriveKey("test-key")
|
|
plaintext := []byte{}
|
|
|
|
encrypted, err := Encrypt(plaintext, key)
|
|
if err != nil {
|
|
t.Fatalf("Encrypt empty plaintext failed: %v", err)
|
|
}
|
|
|
|
decrypted, err := Decrypt(encrypted, key)
|
|
if err != nil {
|
|
t.Fatalf("Decrypt empty plaintext failed: %v", err)
|
|
}
|
|
|
|
if !bytes.Equal(decrypted, plaintext) {
|
|
t.Fatalf("empty plaintext round-trip failed: got %q", decrypted)
|
|
}
|
|
}
|
|
|
|
func TestEncryptInvalidKeyLength(t *testing.T) {
|
|
_, err := Encrypt([]byte("data"), []byte("short-key"))
|
|
if err == nil {
|
|
t.Fatal("expected error for invalid key length")
|
|
}
|
|
}
|
|
|
|
func TestDecryptInvalidKeyLength(t *testing.T) {
|
|
_, err := Decrypt([]byte("some-ciphertext-data"), []byte("short-key"))
|
|
if err == nil {
|
|
t.Fatal("expected error for invalid key length")
|
|
}
|
|
}
|
|
|
|
func TestDecryptTooShortCiphertext(t *testing.T) {
|
|
key := DeriveKey("test-key")
|
|
_, err := Decrypt([]byte("short"), key)
|
|
if err == nil {
|
|
t.Fatal("expected error for too-short ciphertext")
|
|
}
|
|
}
|
|
|
|
func TestDeriveKeyDeterministic(t *testing.T) {
|
|
key1 := DeriveKey("same-passphrase")
|
|
key2 := DeriveKey("same-passphrase")
|
|
if !bytes.Equal(key1, key2) {
|
|
t.Fatal("DeriveKey should be deterministic")
|
|
}
|
|
if len(key1) != 32 {
|
|
t.Fatalf("DeriveKey should return 32 bytes, got %d", len(key1))
|
|
}
|
|
}
|
|
|
|
func TestDeriveKeyDifferentPassphrases(t *testing.T) {
|
|
key1 := DeriveKey("passphrase-one")
|
|
key2 := DeriveKey("passphrase-two")
|
|
if bytes.Equal(key1, key2) {
|
|
t.Fatal("different passphrases should produce different keys")
|
|
}
|
|
}
|
|
|
|
func TestEncryptIfKeySet_WithKey(t *testing.T) {
|
|
key := DeriveKey("test-key")
|
|
plaintext := []byte("config data")
|
|
|
|
result, wasEncrypted, err := EncryptIfKeySet(plaintext, key)
|
|
if err != nil {
|
|
t.Fatalf("EncryptIfKeySet failed: %v", err)
|
|
}
|
|
if !wasEncrypted {
|
|
t.Fatal("expected wasEncrypted=true when key provided")
|
|
}
|
|
if bytes.Equal(result, plaintext) {
|
|
t.Fatal("result should be encrypted")
|
|
}
|
|
|
|
decrypted, err := DecryptIfKeySet(result, key)
|
|
if err != nil {
|
|
t.Fatalf("DecryptIfKeySet failed: %v", err)
|
|
}
|
|
if !bytes.Equal(decrypted, plaintext) {
|
|
t.Fatalf("round-trip failed: got %q", decrypted)
|
|
}
|
|
}
|
|
|
|
func TestEncryptIfKeySet_NilKey(t *testing.T) {
|
|
plaintext := []byte("config data")
|
|
|
|
result, wasEncrypted, err := EncryptIfKeySet(plaintext, nil)
|
|
if err != nil {
|
|
t.Fatalf("EncryptIfKeySet with nil key failed: %v", err)
|
|
}
|
|
if wasEncrypted {
|
|
t.Fatal("expected wasEncrypted=false when key is nil")
|
|
}
|
|
if !bytes.Equal(result, plaintext) {
|
|
t.Fatal("result should be unchanged plaintext when key is nil")
|
|
}
|
|
}
|
|
|
|
func TestDecryptIfKeySet_NilKey(t *testing.T) {
|
|
data := []byte("plaintext config data")
|
|
|
|
result, err := DecryptIfKeySet(data, nil)
|
|
if err != nil {
|
|
t.Fatalf("DecryptIfKeySet with nil key failed: %v", err)
|
|
}
|
|
if !bytes.Equal(result, data) {
|
|
t.Fatal("result should be unchanged when key is nil")
|
|
}
|
|
}
|
|
|
|
func TestEncryptProducesDifferentCiphertexts(t *testing.T) {
|
|
key := DeriveKey("test-key")
|
|
plaintext := []byte("same data")
|
|
|
|
enc1, _ := Encrypt(plaintext, key)
|
|
enc2, _ := Encrypt(plaintext, key)
|
|
|
|
if bytes.Equal(enc1, enc2) {
|
|
t.Fatal("encrypting same plaintext twice should produce different ciphertexts (random nonce)")
|
|
}
|
|
}
|