package crypto_test import ( "bytes" "crypto/rand" "strings" "testing" "git.tukangketik.id/swanadiva/hostkeeper/pkg/crypto" ) // 1.1 DeriveKey determinism — same password + salt → same key func TestDeriveKeyDeterminism(t *testing.T) { salt := []byte("1234567890123456") key1 := crypto.DeriveKey("password", salt) key2 := crypto.DeriveKey("password", salt) if !bytes.Equal(key1, key2) { t.Error("DeriveKey should return same key for same password + salt") } } // 1.2 DeriveKey password variation — different password → different key func TestDeriveKeyPasswordVariation(t *testing.T) { salt := []byte("1234567890123456") key1 := crypto.DeriveKey("password1", salt) key2 := crypto.DeriveKey("password2", salt) if bytes.Equal(key1, key2) { t.Error("DeriveKey should return different keys for different passwords") } } // 1.3 DeriveKey salt variation — different salt → different key func TestDeriveKeySaltVariation(t *testing.T) { key1 := crypto.DeriveKey("password", []byte("1234567890123456")) key2 := crypto.DeriveKey("password", []byte("6543210987654321")) if bytes.Equal(key1, key2) { t.Error("DeriveKey should return different keys for different salts") } } // 1.4 DeriveKey empty password — no panic, valid key length func TestDeriveKeyEmptyPassword(t *testing.T) { salt := []byte("1234567890123456") key := crypto.DeriveKey("", salt) if len(key) != crypto.KeyLength { t.Errorf("DeriveKey with empty password should return %d bytes, got %d", crypto.KeyLength, len(key)) } } // 1.5 Encrypt/Decrypt round-trip func TestEncryptDecryptRoundTrip(t *testing.T) { plaintext := []byte("hello world") encoded, err := crypto.Encrypt(plaintext, "mypassword") if err != nil { t.Fatalf("Encrypt failed: %v", err) } decoded, err := crypto.Decrypt(encoded, "mypassword") if err != nil { t.Fatalf("Decrypt failed: %v", err) } if !bytes.Equal(plaintext, decoded) { t.Errorf("Round-trip failed: got %q, want %q", decoded, plaintext) } } // 1.6 Encrypt empty plaintext func TestEncryptDecryptEmpty(t *testing.T) { plaintext := []byte("") encoded, err := crypto.Encrypt(plaintext, "password") if err != nil { t.Fatalf("Encrypt failed: %v", err) } decoded, err := crypto.Decrypt(encoded, "password") if err != nil { t.Fatalf("Decrypt failed: %v", err) } if len(decoded) != 0 { t.Errorf("Expected empty plaintext, got %d bytes", len(decoded)) } } // 1.7 Encrypt large data (1MB) func TestEncryptDecryptLargeData(t *testing.T) { plaintext := make([]byte, 1024*1024) if _, err := rand.Read(plaintext); err != nil { t.Fatalf("Failed to generate random data: %v", err) } encoded, err := crypto.Encrypt(plaintext, "password") if err != nil { t.Fatalf("Encrypt failed: %v", err) } decoded, err := crypto.Decrypt(encoded, "password") if err != nil { t.Fatalf("Decrypt failed: %v", err) } if !bytes.Equal(plaintext, decoded) { t.Error("Large data round-trip failed") } } // 1.8 Encrypt unicode func TestEncryptDecryptUnicode(t *testing.T) { plaintext := []byte("こんにちは世界 🌍") encoded, err := crypto.Encrypt(plaintext, "password") if err != nil { t.Fatalf("Encrypt failed: %v", err) } decoded, err := crypto.Decrypt(encoded, "password") if err != nil { t.Fatalf("Decrypt failed: %v", err) } if !bytes.Equal(plaintext, decoded) { t.Errorf("Unicode round-trip failed: got %q, want %q", decoded, plaintext) } } // 1.9 Encrypt with newlines func TestEncryptDecryptNewlines(t *testing.T) { plaintext := []byte("line1\nline2\nline3") encoded, err := crypto.Encrypt(plaintext, "password") if err != nil { t.Fatalf("Encrypt failed: %v", err) } decoded, err := crypto.Decrypt(encoded, "password") if err != nil { t.Fatalf("Decrypt failed: %v", err) } if !bytes.Equal(plaintext, decoded) { t.Errorf("Newlines round-trip failed: got %q, want %q", decoded, plaintext) } } // 1.10 Wrong password → error func TestDecryptWrongPassword(t *testing.T) { plaintext := []byte("secret data") encoded, err := crypto.Encrypt(plaintext, "correct-password") if err != nil { t.Fatalf("Encrypt failed: %v", err) } _, err = crypto.Decrypt(encoded, "wrong-password") if err == nil { t.Error("Decrypt with wrong password should return error") } } // 1.11 Empty password → error func TestDecryptEmptyPassword(t *testing.T) { plaintext := []byte("secret data") encoded, err := crypto.Encrypt(plaintext, "password") if err != nil { t.Fatalf("Encrypt failed: %v", err) } _, err = crypto.Decrypt(encoded, "") if err == nil { t.Error("Decrypt with empty password should return error") } } // 1.12 IsEncrypted valid ciphertext func TestIsEncryptedValid(t *testing.T) { encoded, err := crypto.Encrypt([]byte("test"), "password") if err != nil { t.Fatalf("Encrypt failed: %v", err) } if !crypto.IsEncrypted(encoded) { t.Error("IsEncrypted should return true for valid ciphertext") } } // 1.13 IsEncrypted plaintext func TestIsEncryptedPlaintext(t *testing.T) { if crypto.IsEncrypted("hello world") { t.Error("IsEncrypted should return false for plaintext") } } // 1.14 IsEncrypted empty func TestIsEncryptedEmpty(t *testing.T) { if crypto.IsEncrypted("") { t.Error("IsEncrypted should return false for empty string") } } // 1.15 HashPassword determinism func TestHashPasswordDeterminism(t *testing.T) { hash1 := crypto.HashPassword("mypassword") hash2 := crypto.HashPassword("mypassword") if hash1 != hash2 { t.Error("HashPassword should return same hash for same password") } } // 1.16 HashPassword variation func TestHashPasswordVariation(t *testing.T) { hash1 := crypto.HashPassword("password1") hash2 := crypto.HashPassword("password2") if hash1 == hash2 { t.Error("HashPassword should return different hashes for different passwords") } } // 1.17 Encrypt randomness — same input → different ciphertext func TestEncryptRandomness(t *testing.T) { plaintext := []byte("same input") encoded1, _ := crypto.Encrypt(plaintext, "password") encoded2, _ := crypto.Encrypt(plaintext, "password") if encoded1 == encoded2 { t.Error("Encrypt should produce different ciphertext each time (random salt)") } } // Verify constants func TestConstants(t *testing.T) { if crypto.KeyLength != 32 { t.Errorf("KeyLength = %d, want 32", crypto.KeyLength) } if crypto.SaltLength != 16 { t.Errorf("SaltLength = %d, want 16", crypto.SaltLength) } if crypto.Iterations != 100000 { t.Errorf("Iterations = %d, want 100000", crypto.Iterations) } } // Verify error variables func TestErrorVars(t *testing.T) { if crypto.ErrInvalidPassword == nil { t.Error("ErrInvalidPassword should not be nil") } if crypto.ErrDecryptionFailed == nil { t.Error("ErrDecryptionFailed should not be nil") } if !strings.Contains(crypto.ErrDecryptionFailed.Error(), "decryption failed") { t.Error("ErrDecryptionFailed message should contain 'decryption failed'") } }