Tests are finally also ready
This commit is contained in:
892
pkg/engine/engine_test.go
Normal file
892
pkg/engine/engine_test.go
Normal file
@@ -0,0 +1,892 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdh"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewEngine(t *testing.T) {
|
||||
e := NewEngine()
|
||||
if e == nil {
|
||||
t.Fatal("NewEngine() returned nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateKeyPair(t *testing.T) {
|
||||
e := NewEngine()
|
||||
kp, err := e.GenerateKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKeyPair() error = %v", err)
|
||||
}
|
||||
|
||||
if kp.PrivateKey == nil {
|
||||
t.Error("PrivateKey is nil")
|
||||
}
|
||||
if kp.PublicKey == nil {
|
||||
t.Error("PublicKey is nil")
|
||||
}
|
||||
|
||||
// Verify key lengths (X25519 keys are 32 bytes)
|
||||
if len(kp.PrivateKey.Bytes()) != 32 {
|
||||
t.Errorf("PrivateKey length = %d, want 32", len(kp.PrivateKey.Bytes()))
|
||||
}
|
||||
if len(kp.PublicKey.Bytes()) != 32 {
|
||||
t.Errorf("PublicKey length = %d, want 32", len(kp.PublicKey.Bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateKeyPair_Uniqueness(t *testing.T) {
|
||||
e := NewEngine()
|
||||
kp1, err := e.GenerateKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKeyPair() error = %v", err)
|
||||
}
|
||||
|
||||
kp2, err := e.GenerateKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKeyPair() error = %v", err)
|
||||
}
|
||||
|
||||
if bytes.Equal(kp1.PrivateKey.Bytes(), kp2.PrivateKey.Bytes()) {
|
||||
t.Error("Two generated key pairs have identical private keys")
|
||||
}
|
||||
if bytes.Equal(kp1.PublicKey.Bytes(), kp2.PublicKey.Bytes()) {
|
||||
t.Error("Two generated key pairs have identical public keys")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodePublicKey(t *testing.T) {
|
||||
e := NewEngine()
|
||||
kp, err := e.GenerateKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKeyPair() error = %v", err)
|
||||
}
|
||||
|
||||
encoded := kp.EncodePublicKey()
|
||||
|
||||
// Should be 64 hex characters (32 bytes * 2)
|
||||
if len(encoded) != 64 {
|
||||
t.Errorf("EncodePublicKey() length = %d, want 64", len(encoded))
|
||||
}
|
||||
|
||||
// Should be valid hex
|
||||
_, err = hex.DecodeString(encoded)
|
||||
if err != nil {
|
||||
t.Errorf("EncodePublicKey() produced invalid hex: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodePrivateKey(t *testing.T) {
|
||||
e := NewEngine()
|
||||
kp, err := e.GenerateKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKeyPair() error = %v", err)
|
||||
}
|
||||
|
||||
encoded := kp.EncodePrivateKey()
|
||||
|
||||
// Should be 64 hex characters (32 bytes * 2)
|
||||
if len(encoded) != 64 {
|
||||
t.Errorf("EncodePrivateKey() length = %d, want 64", len(encoded))
|
||||
}
|
||||
|
||||
// Should be valid hex
|
||||
_, err = hex.DecodeString(encoded)
|
||||
if err != nil {
|
||||
t.Errorf("EncodePrivateKey() produced invalid hex: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodePrivateKey(t *testing.T) {
|
||||
e := NewEngine()
|
||||
kp, err := e.GenerateKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKeyPair() error = %v", err)
|
||||
}
|
||||
|
||||
encoded := kp.EncodePrivateKey()
|
||||
decoded, err := e.DecodePrivateKey(encoded)
|
||||
if err != nil {
|
||||
t.Fatalf("DecodePrivateKey() error = %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(decoded.Bytes(), kp.PrivateKey.Bytes()) {
|
||||
t.Error("Decoded private key does not match original")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodePublicKey(t *testing.T) {
|
||||
e := NewEngine()
|
||||
kp, err := e.GenerateKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKeyPair() error = %v", err)
|
||||
}
|
||||
|
||||
encoded := kp.EncodePublicKey()
|
||||
decoded, err := e.DecodePublicKey(encoded)
|
||||
if err != nil {
|
||||
t.Fatalf("DecodePublicKey() error = %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(decoded.Bytes(), kp.PublicKey.Bytes()) {
|
||||
t.Error("Decoded public key does not match original")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodePrivateKey_InvalidHex(t *testing.T) {
|
||||
e := NewEngine()
|
||||
_, err := e.DecodePrivateKey("not-valid-hex!")
|
||||
if err == nil {
|
||||
t.Error("DecodePrivateKey() should error on invalid hex")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodePublicKey_InvalidHex(t *testing.T) {
|
||||
e := NewEngine()
|
||||
_, err := e.DecodePublicKey("not-valid-hex!")
|
||||
if err == nil {
|
||||
t.Error("DecodePublicKey() should error on invalid hex")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodePrivateKey_WrongLength(t *testing.T) {
|
||||
e := NewEngine()
|
||||
// 16 bytes instead of 32
|
||||
shortKey := strings.Repeat("00", 16)
|
||||
_, err := e.DecodePrivateKey(shortKey)
|
||||
if err == nil {
|
||||
t.Error("DecodePrivateKey() should error on wrong length key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodePublicKey_WrongLength(t *testing.T) {
|
||||
e := NewEngine()
|
||||
// 16 bytes instead of 32
|
||||
shortKey := strings.Repeat("00", 16)
|
||||
_, err := e.DecodePublicKey(shortKey)
|
||||
if err == nil {
|
||||
t.Error("DecodePublicKey() should error on wrong length key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryptDecrypt_RoundTrip(t *testing.T) {
|
||||
e := NewEngine()
|
||||
|
||||
// Generate sender and recipient key pairs
|
||||
sender, err := e.GenerateKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKeyPair() for sender error = %v", err)
|
||||
}
|
||||
|
||||
recipient, err := e.GenerateKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKeyPair() for recipient error = %v", err)
|
||||
}
|
||||
|
||||
plaintext := []byte("Hello, World! This is a secret message.")
|
||||
|
||||
// Encrypt
|
||||
ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext)
|
||||
if err != nil {
|
||||
t.Fatalf("Encrypt() error = %v", err)
|
||||
}
|
||||
|
||||
// Decrypt
|
||||
decrypted, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, ciphertext)
|
||||
if err != nil {
|
||||
t.Fatalf("Decrypt() error = %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(decrypted, plaintext) {
|
||||
t.Errorf("Decrypted message = %q, want %q", decrypted, plaintext)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryptDecrypt_EmptyMessage(t *testing.T) {
|
||||
e := NewEngine()
|
||||
|
||||
sender, err := e.GenerateKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKeyPair() for sender error = %v", err)
|
||||
}
|
||||
|
||||
recipient, err := e.GenerateKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKeyPair() for recipient error = %v", err)
|
||||
}
|
||||
|
||||
plaintext := []byte("")
|
||||
|
||||
ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext)
|
||||
if err != nil {
|
||||
t.Fatalf("Encrypt() error = %v", err)
|
||||
}
|
||||
|
||||
decrypted, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, ciphertext)
|
||||
if err != nil {
|
||||
t.Fatalf("Decrypt() error = %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(decrypted, plaintext) {
|
||||
t.Errorf("Decrypted message = %q, want empty string", decrypted)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryptDecrypt_LargeMessage(t *testing.T) {
|
||||
e := NewEngine()
|
||||
|
||||
sender, err := e.GenerateKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKeyPair() for sender error = %v", err)
|
||||
}
|
||||
|
||||
recipient, err := e.GenerateKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKeyPair() for recipient error = %v", err)
|
||||
}
|
||||
|
||||
// Create a 1MB message
|
||||
plaintext := make([]byte, 1024*1024)
|
||||
if _, err := rand.Read(plaintext); err != nil {
|
||||
t.Fatalf("Failed to generate random plaintext: %v", err)
|
||||
}
|
||||
|
||||
ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext)
|
||||
if err != nil {
|
||||
t.Fatalf("Encrypt() error = %v", err)
|
||||
}
|
||||
|
||||
decrypted, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, ciphertext)
|
||||
if err != nil {
|
||||
t.Fatalf("Decrypt() error = %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(decrypted, plaintext) {
|
||||
t.Error("Decrypted large message does not match original")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryptDecrypt_BinaryData(t *testing.T) {
|
||||
e := NewEngine()
|
||||
|
||||
sender, err := e.GenerateKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKeyPair() for sender error = %v", err)
|
||||
}
|
||||
|
||||
recipient, err := e.GenerateKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKeyPair() for recipient error = %v", err)
|
||||
}
|
||||
|
||||
// Binary data with null bytes and special characters
|
||||
plaintext := []byte{0x00, 0x01, 0x02, 0xff, 0xfe, 0x00, 0x00, 0x10, 0x20}
|
||||
|
||||
ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext)
|
||||
if err != nil {
|
||||
t.Fatalf("Encrypt() error = %v", err)
|
||||
}
|
||||
|
||||
decrypted, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, ciphertext)
|
||||
if err != nil {
|
||||
t.Fatalf("Decrypt() error = %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(decrypted, plaintext) {
|
||||
t.Errorf("Decrypted binary data = %v, want %v", decrypted, plaintext)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecrypt_WrongRecipientKey(t *testing.T) {
|
||||
e := NewEngine()
|
||||
|
||||
sender, _ := e.GenerateKeyPair()
|
||||
recipient, _ := e.GenerateKeyPair()
|
||||
wrongRecipient, _ := e.GenerateKeyPair()
|
||||
|
||||
plaintext := []byte("Secret message")
|
||||
ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext)
|
||||
if err != nil {
|
||||
t.Fatalf("Encrypt() error = %v", err)
|
||||
}
|
||||
|
||||
// Try to decrypt with wrong recipient's private key
|
||||
_, err = e.Decrypt(wrongRecipient.PrivateKey, sender.PublicKey, ciphertext)
|
||||
if err == nil {
|
||||
t.Error("Decrypt() should fail with wrong recipient key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecrypt_WrongSenderPublicKey(t *testing.T) {
|
||||
e := NewEngine()
|
||||
|
||||
sender, _ := e.GenerateKeyPair()
|
||||
recipient, _ := e.GenerateKeyPair()
|
||||
wrongSender, _ := e.GenerateKeyPair()
|
||||
|
||||
plaintext := []byte("Secret message")
|
||||
ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext)
|
||||
if err != nil {
|
||||
t.Fatalf("Encrypt() error = %v", err)
|
||||
}
|
||||
|
||||
// Try to decrypt with wrong sender's public key
|
||||
_, err = e.Decrypt(recipient.PrivateKey, wrongSender.PublicKey, ciphertext)
|
||||
if err == nil {
|
||||
t.Error("Decrypt() should fail with wrong sender public key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecrypt_TamperedCiphertext(t *testing.T) {
|
||||
e := NewEngine()
|
||||
|
||||
sender, _ := e.GenerateKeyPair()
|
||||
recipient, _ := e.GenerateKeyPair()
|
||||
|
||||
plaintext := []byte("Secret message")
|
||||
ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext)
|
||||
if err != nil {
|
||||
t.Fatalf("Encrypt() error = %v", err)
|
||||
}
|
||||
|
||||
// Tamper with the ciphertext (flip a bit in the actual ciphertext portion)
|
||||
if len(ciphertext) > 60 {
|
||||
ciphertext[60] ^= 0x01
|
||||
}
|
||||
|
||||
_, err = e.Decrypt(recipient.PrivateKey, sender.PublicKey, ciphertext)
|
||||
if err == nil {
|
||||
t.Error("Decrypt() should fail with tampered ciphertext")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecrypt_TruncatedMessage(t *testing.T) {
|
||||
e := NewEngine()
|
||||
|
||||
sender, _ := e.GenerateKeyPair()
|
||||
recipient, _ := e.GenerateKeyPair()
|
||||
|
||||
plaintext := []byte("Secret message")
|
||||
ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext)
|
||||
if err != nil {
|
||||
t.Fatalf("Encrypt() error = %v", err)
|
||||
}
|
||||
|
||||
// Truncate the message
|
||||
truncated := ciphertext[:len(ciphertext)-10]
|
||||
|
||||
_, err = e.Decrypt(recipient.PrivateKey, sender.PublicKey, truncated)
|
||||
if err == nil {
|
||||
t.Error("Decrypt() should fail with truncated ciphertext")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecrypt_MessageTooShort(t *testing.T) {
|
||||
e := NewEngine()
|
||||
|
||||
sender, _ := e.GenerateKeyPair()
|
||||
recipient, _ := e.GenerateKeyPair()
|
||||
|
||||
// Message shorter than pubKeySize + nonceSize (32 + 24 = 56 bytes)
|
||||
shortMessage := make([]byte, 50)
|
||||
|
||||
_, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, shortMessage)
|
||||
if err == nil {
|
||||
t.Error("Decrypt() should fail with message too short")
|
||||
}
|
||||
if err.Error() != "message too short" {
|
||||
t.Errorf("Decrypt() error = %v, want 'message too short'", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecrypt_InvalidEphemeralPublicKey(t *testing.T) {
|
||||
e := NewEngine()
|
||||
|
||||
sender, _ := e.GenerateKeyPair()
|
||||
recipient, _ := e.GenerateKeyPair()
|
||||
|
||||
// Create a message with invalid ephemeral public key (all zeros)
|
||||
invalidMessage := make([]byte, 100)
|
||||
|
||||
_, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, invalidMessage)
|
||||
// This should fail during decryption due to authentication failure
|
||||
if err == nil {
|
||||
t.Error("Decrypt() should fail with invalid ephemeral public key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncrypt_DifferentCiphertextEachTime(t *testing.T) {
|
||||
e := NewEngine()
|
||||
|
||||
sender, _ := e.GenerateKeyPair()
|
||||
recipient, _ := e.GenerateKeyPair()
|
||||
|
||||
plaintext := []byte("Same message")
|
||||
|
||||
ciphertext1, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext)
|
||||
if err != nil {
|
||||
t.Fatalf("First Encrypt() error = %v", err)
|
||||
}
|
||||
|
||||
ciphertext2, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext)
|
||||
if err != nil {
|
||||
t.Fatalf("Second Encrypt() error = %v", err)
|
||||
}
|
||||
|
||||
// Due to ephemeral keys and random nonce, ciphertexts should be different
|
||||
if bytes.Equal(ciphertext1, ciphertext2) {
|
||||
t.Error("Encrypting same message twice should produce different ciphertexts (due to ephemeral keys)")
|
||||
}
|
||||
|
||||
// But both should decrypt to the same plaintext
|
||||
decrypted1, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, ciphertext1)
|
||||
if err != nil {
|
||||
t.Fatalf("Decrypt first error = %v", err)
|
||||
}
|
||||
decrypted2, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, ciphertext2)
|
||||
if err != nil {
|
||||
t.Fatalf("Decrypt second error = %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(decrypted1, plaintext) || !bytes.Equal(decrypted2, plaintext) {
|
||||
t.Error("Both ciphertexts should decrypt to the same plaintext")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCiphertextFormat(t *testing.T) {
|
||||
e := NewEngine()
|
||||
|
||||
sender, _ := e.GenerateKeyPair()
|
||||
recipient, _ := e.GenerateKeyPair()
|
||||
|
||||
plaintext := []byte("Test message")
|
||||
|
||||
ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext)
|
||||
if err != nil {
|
||||
t.Fatalf("Encrypt() error = %v", err)
|
||||
}
|
||||
|
||||
// Ciphertext format: [EphemeralPub (32)] [Nonce (24)] [Ciphertext + Tag (len(plaintext) + 16)]
|
||||
expectedMinLength := 32 + 24 + len(plaintext) + 16 // 16 is Poly1305 tag size
|
||||
if len(ciphertext) != expectedMinLength {
|
||||
t.Errorf("Ciphertext length = %d, want %d", len(ciphertext), expectedMinLength)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyPairConsistency(t *testing.T) {
|
||||
e := NewEngine()
|
||||
kp, err := e.GenerateKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKeyPair() error = %v", err)
|
||||
}
|
||||
|
||||
// Public key should be derivable from private key
|
||||
derivedPub := kp.PrivateKey.PublicKey()
|
||||
if !bytes.Equal(derivedPub.Bytes(), kp.PublicKey.Bytes()) {
|
||||
t.Error("Public key should be consistent with private key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeDecodeRoundTrip(t *testing.T) {
|
||||
e := NewEngine()
|
||||
kp, err := e.GenerateKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKeyPair() error = %v", err)
|
||||
}
|
||||
|
||||
// Test private key round trip
|
||||
privEncoded := kp.EncodePrivateKey()
|
||||
privDecoded, err := e.DecodePrivateKey(privEncoded)
|
||||
if err != nil {
|
||||
t.Fatalf("DecodePrivateKey() error = %v", err)
|
||||
}
|
||||
|
||||
// Test public key round trip
|
||||
pubEncoded := kp.EncodePublicKey()
|
||||
pubDecoded, err := e.DecodePublicKey(pubEncoded)
|
||||
if err != nil {
|
||||
t.Fatalf("DecodePublicKey() error = %v", err)
|
||||
}
|
||||
|
||||
// Use decoded keys for encryption/decryption
|
||||
recipient, _ := e.GenerateKeyPair()
|
||||
plaintext := []byte("Round trip test")
|
||||
|
||||
ciphertext, err := e.Encrypt(privDecoded, recipient.PublicKey, plaintext)
|
||||
if err != nil {
|
||||
t.Fatalf("Encrypt with decoded key error = %v", err)
|
||||
}
|
||||
|
||||
decrypted, err := e.Decrypt(recipient.PrivateKey, pubDecoded, ciphertext)
|
||||
if err != nil {
|
||||
t.Fatalf("Decrypt with decoded key error = %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(decrypted, plaintext) {
|
||||
t.Error("Decoded keys should work correctly for encryption/decryption")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleRecipients(t *testing.T) {
|
||||
e := NewEngine()
|
||||
|
||||
sender, _ := e.GenerateKeyPair()
|
||||
recipient1, _ := e.GenerateKeyPair()
|
||||
recipient2, _ := e.GenerateKeyPair()
|
||||
|
||||
plaintext := []byte("Message for multiple recipients")
|
||||
|
||||
// Encrypt for recipient1
|
||||
ciphertext1, err := e.Encrypt(sender.PrivateKey, recipient1.PublicKey, plaintext)
|
||||
if err != nil {
|
||||
t.Fatalf("Encrypt for recipient1 error = %v", err)
|
||||
}
|
||||
|
||||
// Encrypt for recipient2
|
||||
ciphertext2, err := e.Encrypt(sender.PrivateKey, recipient2.PublicKey, plaintext)
|
||||
if err != nil {
|
||||
t.Fatalf("Encrypt for recipient2 error = %v", err)
|
||||
}
|
||||
|
||||
// Both should be able to decrypt their own messages
|
||||
decrypted1, err := e.Decrypt(recipient1.PrivateKey, sender.PublicKey, ciphertext1)
|
||||
if err != nil {
|
||||
t.Fatalf("Decrypt for recipient1 error = %v", err)
|
||||
}
|
||||
if !bytes.Equal(decrypted1, plaintext) {
|
||||
t.Error("Recipient1 should be able to decrypt their message")
|
||||
}
|
||||
|
||||
decrypted2, err := e.Decrypt(recipient2.PrivateKey, sender.PublicKey, ciphertext2)
|
||||
if err != nil {
|
||||
t.Fatalf("Decrypt for recipient2 error = %v", err)
|
||||
}
|
||||
if !bytes.Equal(decrypted2, plaintext) {
|
||||
t.Error("Recipient2 should be able to decrypt their message")
|
||||
}
|
||||
|
||||
// Cross decryption should fail
|
||||
_, err = e.Decrypt(recipient1.PrivateKey, sender.PublicKey, ciphertext2)
|
||||
if err == nil {
|
||||
t.Error("Recipient1 should NOT be able to decrypt recipient2's message")
|
||||
}
|
||||
|
||||
_, err = e.Decrypt(recipient2.PrivateKey, sender.PublicKey, ciphertext1)
|
||||
if err == nil {
|
||||
t.Error("Recipient2 should NOT be able to decrypt recipient1's message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnicodeMessage(t *testing.T) {
|
||||
e := NewEngine()
|
||||
|
||||
sender, _ := e.GenerateKeyPair()
|
||||
recipient, _ := e.GenerateKeyPair()
|
||||
|
||||
// Unicode message with various scripts
|
||||
plaintext := []byte("Hello 世界! Привет мир! 🔐🔑")
|
||||
|
||||
ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext)
|
||||
if err != nil {
|
||||
t.Fatalf("Encrypt() error = %v", err)
|
||||
}
|
||||
|
||||
decrypted, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, ciphertext)
|
||||
if err != nil {
|
||||
t.Fatalf("Decrypt() error = %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(decrypted, plaintext) {
|
||||
t.Errorf("Decrypted unicode message = %q, want %q", decrypted, plaintext)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark tests
|
||||
func BenchmarkGenerateKeyPair(b *testing.B) {
|
||||
e := NewEngine()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := e.GenerateKeyPair()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncrypt(b *testing.B) {
|
||||
e := NewEngine()
|
||||
sender, _ := e.GenerateKeyPair()
|
||||
recipient, _ := e.GenerateKeyPair()
|
||||
plaintext := []byte("Benchmark encryption test message")
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecrypt(b *testing.B) {
|
||||
e := NewEngine()
|
||||
sender, _ := e.GenerateKeyPair()
|
||||
recipient, _ := e.GenerateKeyPair()
|
||||
plaintext := []byte("Benchmark decryption test message")
|
||||
ciphertext, _ := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, ciphertext)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncryptLargeMessage(b *testing.B) {
|
||||
e := NewEngine()
|
||||
sender, _ := e.GenerateKeyPair()
|
||||
recipient, _ := e.GenerateKeyPair()
|
||||
plaintext := make([]byte, 1024*1024) // 1MB
|
||||
rand.Read(plaintext)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test that verifies the authentication property - message must come from correct sender
|
||||
func TestSenderAuthentication(t *testing.T) {
|
||||
e := NewEngine()
|
||||
|
||||
sender, _ := e.GenerateKeyPair()
|
||||
recipient, _ := e.GenerateKeyPair()
|
||||
imposter, _ := e.GenerateKeyPair()
|
||||
|
||||
plaintext := []byte("Authenticated message")
|
||||
|
||||
// Sender encrypts
|
||||
ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext)
|
||||
if err != nil {
|
||||
t.Fatalf("Encrypt() error = %v", err)
|
||||
}
|
||||
|
||||
// Recipient tries to decrypt thinking it's from imposter - should fail
|
||||
_, err = e.Decrypt(recipient.PrivateKey, imposter.PublicKey, ciphertext)
|
||||
if err == nil {
|
||||
t.Error("Decryption should fail when wrong sender public key is provided")
|
||||
}
|
||||
|
||||
// Recipient decrypts with correct sender public key - should succeed
|
||||
decrypted, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, ciphertext)
|
||||
if err != nil {
|
||||
t.Fatalf("Decrypt() with correct sender should succeed: %v", err)
|
||||
}
|
||||
if !bytes.Equal(decrypted, plaintext) {
|
||||
t.Error("Decrypted message should match plaintext")
|
||||
}
|
||||
}
|
||||
|
||||
// Test forward secrecy by verifying ephemeral keys are different each encryption
|
||||
func TestForwardSecrecy(t *testing.T) {
|
||||
e := NewEngine()
|
||||
|
||||
sender, _ := e.GenerateKeyPair()
|
||||
recipient, _ := e.GenerateKeyPair()
|
||||
|
||||
plaintext := []byte("Forward secrecy test")
|
||||
|
||||
ciphertext1, _ := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext)
|
||||
ciphertext2, _ := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext)
|
||||
|
||||
// Extract ephemeral public keys (first 32 bytes)
|
||||
ephemeralPub1 := ciphertext1[:32]
|
||||
ephemeralPub2 := ciphertext2[:32]
|
||||
|
||||
// Ephemeral keys should be different for each encryption
|
||||
if bytes.Equal(ephemeralPub1, ephemeralPub2) {
|
||||
t.Error("Ephemeral public keys should be different for each encryption (forward secrecy)")
|
||||
}
|
||||
}
|
||||
|
||||
// Test with known test vectors (useful for cross-implementation testing)
|
||||
func TestDecodeKnownKeys(t *testing.T) {
|
||||
e := NewEngine()
|
||||
|
||||
// Generate a key pair and verify it can be encoded and decoded
|
||||
kp, err := e.GenerateKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKeyPair() error = %v", err)
|
||||
}
|
||||
|
||||
privHex := kp.EncodePrivateKey()
|
||||
pubHex := kp.EncodePublicKey()
|
||||
|
||||
// Verify the hex strings are lowercase
|
||||
if privHex != strings.ToLower(privHex) {
|
||||
t.Error("Private key hex should be lowercase")
|
||||
}
|
||||
if pubHex != strings.ToLower(pubHex) {
|
||||
t.Error("Public key hex should be lowercase")
|
||||
}
|
||||
|
||||
// Verify decoding
|
||||
priv, err := e.DecodePrivateKey(privHex)
|
||||
if err != nil {
|
||||
t.Fatalf("DecodePrivateKey() error = %v", err)
|
||||
}
|
||||
|
||||
pub, err := e.DecodePublicKey(pubHex)
|
||||
if err != nil {
|
||||
t.Fatalf("DecodePublicKey() error = %v", err)
|
||||
}
|
||||
|
||||
// Verify the decoded public key matches derived public key
|
||||
if !bytes.Equal(priv.PublicKey().Bytes(), pub.Bytes()) {
|
||||
t.Error("Decoded public key should match public key derived from decoded private key")
|
||||
}
|
||||
}
|
||||
|
||||
// Test edge case: decode with uppercase hex (should work since hex.DecodeString handles both)
|
||||
func TestDecodeUppercaseHex(t *testing.T) {
|
||||
e := NewEngine()
|
||||
|
||||
kp, _ := e.GenerateKeyPair()
|
||||
|
||||
privHex := strings.ToUpper(kp.EncodePrivateKey())
|
||||
pubHex := strings.ToUpper(kp.EncodePublicKey())
|
||||
|
||||
priv, err := e.DecodePrivateKey(privHex)
|
||||
if err != nil {
|
||||
t.Fatalf("DecodePrivateKey() should handle uppercase hex: %v", err)
|
||||
}
|
||||
|
||||
pub, err := e.DecodePublicKey(pubHex)
|
||||
if err != nil {
|
||||
t.Fatalf("DecodePublicKey() should handle uppercase hex: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(priv.Bytes(), kp.PrivateKey.Bytes()) {
|
||||
t.Error("Decoded private key should match original")
|
||||
}
|
||||
if !bytes.Equal(pub.Bytes(), kp.PublicKey.Bytes()) {
|
||||
t.Error("Decoded public key should match original")
|
||||
}
|
||||
}
|
||||
|
||||
// Fuzz-like test with random message sizes
|
||||
func TestRandomMessageSizes(t *testing.T) {
|
||||
e := NewEngine()
|
||||
sender, _ := e.GenerateKeyPair()
|
||||
recipient, _ := e.GenerateKeyPair()
|
||||
|
||||
sizes := []int{0, 1, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 256, 512, 1000, 4096}
|
||||
|
||||
for _, size := range sizes {
|
||||
t.Run(string(rune(size)), func(t *testing.T) {
|
||||
plaintext := make([]byte, size)
|
||||
if size > 0 {
|
||||
rand.Read(plaintext)
|
||||
}
|
||||
|
||||
ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext)
|
||||
if err != nil {
|
||||
t.Fatalf("Encrypt() with size %d error = %v", size, err)
|
||||
}
|
||||
|
||||
decrypted, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, ciphertext)
|
||||
if err != nil {
|
||||
t.Fatalf("Decrypt() with size %d error = %v", size, err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(decrypted, plaintext) {
|
||||
t.Errorf("Round trip failed for size %d", size)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Test concurrent encryption/decryption
|
||||
func TestConcurrentOperations(t *testing.T) {
|
||||
e := NewEngine()
|
||||
sender, _ := e.GenerateKeyPair()
|
||||
recipient, _ := e.GenerateKeyPair()
|
||||
|
||||
const numGoroutines = 100
|
||||
done := make(chan bool, numGoroutines)
|
||||
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
go func(id int) {
|
||||
plaintext := []byte("Concurrent message " + string(rune('A'+id%26)))
|
||||
|
||||
ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext)
|
||||
if err != nil {
|
||||
t.Errorf("Goroutine %d: Encrypt() error = %v", id, err)
|
||||
done <- false
|
||||
return
|
||||
}
|
||||
|
||||
decrypted, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, ciphertext)
|
||||
if err != nil {
|
||||
t.Errorf("Goroutine %d: Decrypt() error = %v", id, err)
|
||||
done <- false
|
||||
return
|
||||
}
|
||||
|
||||
if !bytes.Equal(decrypted, plaintext) {
|
||||
t.Errorf("Goroutine %d: decrypted != plaintext", id)
|
||||
done <- false
|
||||
return
|
||||
}
|
||||
|
||||
done <- true
|
||||
}(i)
|
||||
}
|
||||
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
<-done
|
||||
}
|
||||
}
|
||||
|
||||
// Test self-encryption (sender encrypts to themselves)
|
||||
func TestSelfEncryption(t *testing.T) {
|
||||
e := NewEngine()
|
||||
user, _ := e.GenerateKeyPair()
|
||||
|
||||
plaintext := []byte("Message to myself")
|
||||
|
||||
ciphertext, err := e.Encrypt(user.PrivateKey, user.PublicKey, plaintext)
|
||||
if err != nil {
|
||||
t.Fatalf("Encrypt() error = %v", err)
|
||||
}
|
||||
|
||||
decrypted, err := e.Decrypt(user.PrivateKey, user.PublicKey, ciphertext)
|
||||
if err != nil {
|
||||
t.Fatalf("Decrypt() error = %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(decrypted, plaintext) {
|
||||
t.Error("Self-encryption should work correctly")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure interface compatibility (Engine implements expected methods)
|
||||
func TestEngineInterface(t *testing.T) {
|
||||
e := NewEngine()
|
||||
|
||||
// Test that all public methods exist and have correct signatures
|
||||
var _ func() (*KeyPair, error) = e.GenerateKeyPair
|
||||
var _ func(string) (*ecdh.PrivateKey, error) = e.DecodePrivateKey
|
||||
var _ func(string) (*ecdh.PublicKey, error) = e.DecodePublicKey
|
||||
var _ func(*ecdh.PrivateKey, *ecdh.PublicKey, []byte) ([]byte, error) = e.Encrypt
|
||||
var _ func(*ecdh.PrivateKey, *ecdh.PublicKey, []byte) ([]byte, error) = e.Decrypt
|
||||
}
|
||||
Reference in New Issue
Block a user