Skip to main content
Glama

Keeper Secrets Manager - MCP

security_test.go8.68 kB
package crypto import ( "bytes" "crypto/rand" "encoding/base64" "strings" "testing" ) // TestEncryptor_SecurityProperties tests cryptographic security properties func TestEncryptor_SecurityProperties(t *testing.T) { encryptor := NewEncryptor("test-password") tests := []struct { name string test func(*testing.T) }{ { name: "Different ciphertexts for same plaintext", test: func(t *testing.T) { plaintext := "sensitive data" // Encrypt the same plaintext multiple times ciphertexts := make([]string, 10) for i := 0; i < 10; i++ { encrypted, err := encryptor.EncryptString(plaintext) if err != nil { t.Fatalf("Encryption failed: %v", err) } ciphertexts[i] = encrypted } // All ciphertexts should be different (due to random nonce) for i := 0; i < len(ciphertexts); i++ { for j := i + 1; j < len(ciphertexts); j++ { if ciphertexts[i] == ciphertexts[j] { t.Error("Same plaintext produced identical ciphertexts - nonce reuse detected!") } } } }, }, { name: "Ciphertext tampering detection", test: func(t *testing.T) { plaintext := "sensitive data" encrypted, err := encryptor.EncryptString(plaintext) if err != nil { t.Fatalf("Encryption failed: %v", err) } // Decode the base64 ciphertext, err := base64.StdEncoding.DecodeString(encrypted) if err != nil { t.Fatalf("Failed to decode ciphertext: %v", err) } // Tamper with the ciphertext tamperPositions := []int{0, len(ciphertext) / 2, len(ciphertext) - 1} for _, pos := range tamperPositions { if pos < len(ciphertext) { tampered := make([]byte, len(ciphertext)) copy(tampered, ciphertext) tampered[pos] ^= 0xFF // Flip all bits at position tamperedB64 := base64.StdEncoding.EncodeToString(tampered) _, err := encryptor.DecryptString(tamperedB64) if err == nil { t.Error("Decryption succeeded with tampered ciphertext - authentication bypass!") } } } }, }, { name: "Key derivation strength", test: func(t *testing.T) { // Test that similar passphrases produce completely different keys passphrases := []string{ "password123", "password124", "Password123", "password123!", } encryptors := make([]*Encryptor, len(passphrases)) for i, pass := range passphrases { enc := NewEncryptor(pass) encryptors[i] = enc } // Encrypt the same data with each encryptor plaintext := "test data" for i, enc1 := range encryptors { encrypted1, _ := enc1.EncryptString(plaintext) // Try to decrypt with other encryptors for j, enc2 := range encryptors { if i != j { _, err := enc2.DecryptString(encrypted1) if err == nil { t.Errorf("Encryptor with passphrase %q decrypted data from passphrase %q", passphrases[j], passphrases[i]) } } } } }, }, { name: "Timing attack resistance", test: func(t *testing.T) { // This is a basic check - true timing attack tests require specialized tools // We're mainly ensuring the code doesn't have obvious timing leaks validPassword := "correct-password" wrongPassword := "wrong-password" enc1 := NewEncryptor(validPassword) enc2 := NewEncryptor(wrongPassword) plaintext := "test data" encrypted, _ := enc1.EncryptString(plaintext) // Both should fail/succeed in roughly similar time // (This is a simplified test - real timing tests need statistical analysis) _, err1 := enc1.DecryptString(encrypted) _, err2 := enc2.DecryptString(encrypted) if err1 != nil { t.Error("Valid password failed to decrypt") } if err2 == nil { t.Error("Wrong password succeeded in decryption") } }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.test(t) }) } } // TestNonceUniqueness verifies nonce generation is truly random func TestNonceUniqueness(t *testing.T) { nonces := make(map[string]bool) iterations := 10000 for i := 0; i < iterations; i++ { nonce := make([]byte, NonceSize) if _, err := rand.Read(nonce); err != nil { t.Fatalf("Failed to generate nonce: %v", err) } nonceStr := string(nonce) if nonces[nonceStr] { t.Fatalf("Nonce collision detected after %d iterations!", i) } nonces[nonceStr] = true } } // TestKeyDerivationIterations ensures sufficient iterations for PBKDF2 func TestKeyDerivationIterations(t *testing.T) { // NIST recommends at least 10,000 iterations, we use 100,000 if Iterations < 10000 { t.Errorf("PBKDF2 iterations too low: %d (minimum recommended: 10,000)", Iterations) } } // TestEncryptedDataIntegrity verifies encrypted data structure func TestEncryptedDataIntegrity(t *testing.T) { enc := NewEncryptor("test-password") plaintext := []byte("test data") encrypted, err := enc.Encrypt(plaintext) if err != nil { t.Fatalf("Encryption failed: %v", err) } // Verify all components are present and have correct sizes if len(encrypted.Salt) != SaltSize { t.Errorf("Salt size incorrect: got %d, want %d", len(encrypted.Salt), SaltSize) } if len(encrypted.Nonce) != NonceSize { t.Errorf("Nonce size incorrect: got %d, want %d", len(encrypted.Nonce), NonceSize) } if len(encrypted.Ciphertext) == 0 { t.Error("Ciphertext is empty") } // Ciphertext should be plaintext length + GCM tag size (16 bytes) expectedLen := len(plaintext) + 16 if len(encrypted.Ciphertext) != expectedLen { t.Errorf("Ciphertext length incorrect: got %d, want %d", len(encrypted.Ciphertext), expectedLen) } } // TestLargeDataEncryption tests encryption of large data func TestLargeDataEncryption(t *testing.T) { enc := NewEncryptor("test-password") // Test various sizes sizes := []int{ 1024, // 1KB 1024 * 1024, // 1MB 10 * 1024 * 1024, // 10MB } for _, size := range sizes { t.Run(strings.Replace("Size_%dB", "%d", string(rune(size)), 1), func(t *testing.T) { // Generate random data plaintext := make([]byte, size) if _, err := rand.Read(plaintext); err != nil { t.Fatalf("Failed to generate random data: %v", err) } // Encrypt encrypted, err := enc.Encrypt(plaintext) if err != nil { t.Fatalf("Encryption failed for size %d: %v", size, err) } // Decrypt decrypted, err := enc.Decrypt(encrypted) if err != nil { t.Fatalf("Decryption failed for size %d: %v", size, err) } // Verify if !bytes.Equal(plaintext, decrypted) { t.Errorf("Decrypted data doesn't match original for size %d", size) } }) } } // TestPasswordComplexity ensures the crypto module handles various password types func TestPasswordComplexity(t *testing.T) { passwords := []string{ "", // Empty password "a", // Single character "password", // Simple password "P@ssw0rd!123", // Complex password "这是一个中文密码", // Unicode password "🔐🔑🛡️", // Emoji password strings.Repeat("A", 1000), // Very long password "password with spaces and special chars !@#$%^&*()", } plaintext := "test data" for _, password := range passwords { t.Run("password_complexity", func(t *testing.T) { enc := NewEncryptor(password) // Should be able to encrypt/decrypt with any password encrypted, err := enc.EncryptString(plaintext) if err != nil { t.Fatalf("Failed to encrypt with password %q: %v", password, err) } decrypted, err := enc.DecryptString(encrypted) if err != nil { t.Fatalf("Failed to decrypt with password %q: %v", password, err) } if decrypted != plaintext { t.Errorf("Decryption mismatch with password %q", password) } }) } } // TestConcurrentEncryption tests thread safety func TestConcurrentEncryption(t *testing.T) { enc := NewEncryptor("test-password") done := make(chan bool) errors := make(chan error, 100) // Run 100 concurrent encryptions for i := 0; i < 100; i++ { go func(id int) { defer func() { done <- true }() plaintext := []byte(strings.Repeat("data", id+1)) encrypted, err := enc.Encrypt(plaintext) if err != nil { errors <- err return } decrypted, err := enc.Decrypt(encrypted) if err != nil { errors <- err return } if !bytes.Equal(plaintext, decrypted) { errors <- err } }(i) } // Wait for all goroutines for i := 0; i < 100; i++ { <-done } close(errors) // Check for errors for err := range errors { if err != nil { t.Errorf("Concurrent encryption error: %v", err) } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Keeper-Security/keeper-mcp-golang-docker'

If you have feedback or need assistance with the MCP directory API, please join our Discord server