893 lines
24 KiB
Go
893 lines
24 KiB
Go
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
|
|
}
|