diff --git a/examples/main.go b/examples/main.go index 2fa3c14..3e4c082 100644 --- a/examples/main.go +++ b/examples/main.go @@ -182,14 +182,14 @@ func demoRSA() { // 2. Encrypt a message message := []byte("This is a secret message for RSA.") fmt.Printf("\nOriginal message: %s\n", message) - ciphertext, err := cryptService.EncryptRSA(publicKey, message) + ciphertext, err := cryptService.EncryptRSA(publicKey, message, nil) if err != nil { log.Fatalf("Failed to encrypt with RSA: %v", err) } fmt.Printf("Encrypted ciphertext (base64): %s\n", base64.StdEncoding.EncodeToString(ciphertext)) // 3. Decrypt the message - decrypted, err := cryptService.DecryptRSA(privateKey, ciphertext) + decrypted, err := cryptService.DecryptRSA(privateKey, ciphertext, nil) if err != nil { log.Fatalf("Failed to decrypt with RSA: %v", err) } diff --git a/pkg/crypt/crypt_test.go b/pkg/crypt/crypt_test.go deleted file mode 100644 index bead19f..0000000 --- a/pkg/crypt/crypt_test.go +++ /dev/null @@ -1,109 +0,0 @@ -package crypt - -import ( - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -var service = NewService() - -// --- Hashing Tests --- - -func TestHash_Good(t *testing.T) { - payload := "hello" - // Test all supported hash types - for _, hashType := range []HashType{LTHN, SHA512, SHA256, SHA1, MD5} { - hash := service.Hash(hashType, payload) - assert.NotEmpty(t, hash, "Hash should not be empty for type %s", hashType) - } -} - -func TestHash_Bad(t *testing.T) { - // Using an unsupported hash type should default to SHA256 - hash := service.Hash("unsupported", "hello") - expectedHash := service.Hash(SHA256, "hello") - assert.Equal(t, expectedHash, hash) -} - -func TestHash_Ugly(t *testing.T) { - // Test with potentially problematic inputs - testCases := []string{ - "", // Empty string - " ", // Whitespace - "\x00\x01\x02\x03\x04", // Null bytes - strings.Repeat("a", 1024*1024), // Large payload (1MB) - "こんにちは", // Unicode characters - } - - for _, tc := range testCases { - for _, hashType := range []HashType{LTHN, SHA512, SHA256, SHA1, MD5} { - hash := service.Hash(hashType, tc) - assert.NotEmpty(t, hash, "Hash for ugly input should not be empty for type %s", hashType) - } - } -} - -// --- Checksum Tests --- - -// Luhn Tests -func TestLuhn_Good(t *testing.T) { - assert.True(t, service.Luhn("79927398713")) -} - -func TestLuhn_Bad(t *testing.T) { - assert.False(t, service.Luhn("79927398714"), "Should fail for incorrect checksum") - assert.False(t, service.Luhn("7992739871a"), "Should fail for non-numeric input") -} - -func TestLuhn_Ugly(t *testing.T) { - assert.False(t, service.Luhn(""), "Should be false for empty string") - assert.False(t, service.Luhn(" 1 2 3 "), "Should handle spaces but result in false") -} - -// Fletcher16 Tests -func TestFletcher16_Good(t *testing.T) { - assert.Equal(t, uint16(0xC8F0), service.Fletcher16("abcde")) - assert.Equal(t, uint16(0x2057), service.Fletcher16("abcdef")) - assert.Equal(t, uint16(0x0627), service.Fletcher16("abcdefgh")) -} - -func TestFletcher16_Bad(t *testing.T) { - // No obviously "bad" inputs that don't fall into "ugly" - // For Fletcher, any string is a valid input. -} - -func TestFletcher16_Ugly(t *testing.T) { - assert.Equal(t, uint16(0), service.Fletcher16(""), "Checksum of empty string should be 0") -} - -// Fletcher32 Tests -func TestFletcher32_Good(t *testing.T) { - assert.Equal(t, uint32(0xF04FC729), service.Fletcher32("abcde")) - assert.Equal(t, uint32(0x56502D2A), service.Fletcher32("abcdef")) - assert.Equal(t, uint32(0xEBE19591), service.Fletcher32("abcdefgh")) -} - -func TestFletcher32_Bad(t *testing.T) { - // Any string is a valid input. -} - -func TestFletcher32_Ugly(t *testing.T) { - assert.Equal(t, uint32(0), service.Fletcher32(""), "Checksum of empty string should be 0") -} - -// Fletcher64 Tests -func TestFletcher64_Good(t *testing.T) { - assert.Equal(t, uint64(0xc8c6c527646362c6), service.Fletcher64("abcde")) - assert.Equal(t, uint64(0xc8c72b276463c8c6), service.Fletcher64("abcdef")) - assert.Equal(t, uint64(0x312e2b28cccac8c6), service.Fletcher64("abcdefgh")) -} - -func TestFletcher64_Bad(t *testing.T) { - // Any string is a valid input. -} - -func TestFletcher64_Ugly(t *testing.T) { - assert.Equal(t, uint64(0), service.Fletcher64(""), "Checksum of empty string should be 0") -} diff --git a/pkg/enchantrix/enchantrix_test.go b/pkg/enchantrix/enchantrix_test.go deleted file mode 100644 index 71b5901..0000000 --- a/pkg/enchantrix/enchantrix_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package enchantrix_test - -import ( - "testing" - - "github.com/Snider/Enchantrix/pkg/enchantrix" - "github.com/stretchr/testify/assert" -) - -func TestTransmute(t *testing.T) { - data := []byte("hello") - sigils := []enchantrix.Sigil{ - &enchantrix.ReverseSigil{}, - &enchantrix.HexSigil{}, - } - result, err := enchantrix.Transmute(data, sigils) - assert.NoError(t, err) - assert.Equal(t, "6f6c6c6568", string(result)) -} diff --git a/pkg/enchantrix/factory_test.go b/pkg/enchantrix/factory_test.go deleted file mode 100644 index 15c7352..0000000 --- a/pkg/enchantrix/factory_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package enchantrix_test - -import ( - "testing" - - "github.com/Snider/Enchantrix/pkg/enchantrix" - "github.com/stretchr/testify/assert" -) - -func TestNewSigil(t *testing.T) { - t.Run("ValidSigils", func(t *testing.T) { - validNames := []string{ - "reverse", "hex", "base64", "gzip", "json", "json-indent", - "md4", "md5", "sha1", "sha224", "sha256", "sha384", "sha512", - "ripemd160", "sha3-224", "sha3-256", "sha3-384", "sha3-512", - "sha512-224", "sha512-256", "blake2s-256", "blake2b-256", - "blake2b-384", "blake2b-512", - } - for _, name := range validNames { - sigil, err := enchantrix.NewSigil(name) - assert.NoError(t, err) - assert.NotNil(t, sigil) - } - }) - - t.Run("InvalidSigil", func(t *testing.T) { - sigil, err := enchantrix.NewSigil("invalid-sigil-name") - assert.Error(t, err) - assert.Nil(t, sigil) - assert.Contains(t, err.Error(), "unknown sigil name") - }) -} diff --git a/pkg/enchantrix/sigils_test.go b/pkg/enchantrix/sigils_test.go deleted file mode 100644 index ad9715e..0000000 --- a/pkg/enchantrix/sigils_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package enchantrix_test - -import ( - "crypto" - "encoding/hex" - "testing" - - "github.com/Snider/Enchantrix/pkg/enchantrix" - "github.com/stretchr/testify/assert" -) - -func TestReverseSigil(t *testing.T) { - s := &enchantrix.ReverseSigil{} - data := []byte("hello") - reversed, err := s.In(data) - assert.NoError(t, err) - assert.Equal(t, "olleh", string(reversed)) - original, err := s.Out(reversed) - assert.NoError(t, err) - assert.Equal(t, "hello", string(original)) -} - -func TestHexSigil(t *testing.T) { - s := &enchantrix.HexSigil{} - data := []byte("hello") - encoded, err := s.In(data) - assert.NoError(t, err) - assert.Equal(t, "68656c6c6f", string(encoded)) - decoded, err := s.Out(encoded) - assert.NoError(t, err) - assert.Equal(t, "hello", string(decoded)) -} - -func TestBase64Sigil(t *testing.T) { - s := &enchantrix.Base64Sigil{} - data := []byte("hello") - encoded, err := s.In(data) - assert.NoError(t, err) - assert.Equal(t, "aGVsbG8=", string(encoded)) - decoded, err := s.Out(encoded) - assert.NoError(t, err) - assert.Equal(t, "hello", string(decoded)) -} - -func TestGzipSigil(t *testing.T) { - s := &enchantrix.GzipSigil{} - data := []byte("hello") - compressed, err := s.In(data) - assert.NoError(t, err) - assert.NotEqual(t, data, compressed) - decompressed, err := s.Out(compressed) - assert.NoError(t, err) - assert.Equal(t, "hello", string(decompressed)) -} - -func TestJSONSigil(t *testing.T) { - s := &enchantrix.JSONSigil{Indent: true} - data := []byte(`{"hello":"world"}`) - indented, err := s.In(data) - assert.NoError(t, err) - assert.Equal(t, "{\n \"hello\": \"world\"\n}", string(indented)) - s.Indent = false - compacted, err := s.In(indented) - assert.NoError(t, err) - assert.Equal(t, `{"hello":"world"}`, string(compacted)) -} - -func TestHashSigil(t *testing.T) { - s := enchantrix.NewHashSigil(crypto.SHA256) - data := []byte("hello") - hashed, err := s.In(data) - assert.NoError(t, err) - expectedHash := "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" - assert.Equal(t, expectedHash, hex.EncodeToString(hashed)) - unhashed, err := s.Out(hashed) - assert.NoError(t, err) - assert.Equal(t, hashed, unhashed) -} diff --git a/pkg/trix/fuzz_test.go b/pkg/trix/fuzz_test.go deleted file mode 100644 index 28565d7..0000000 --- a/pkg/trix/fuzz_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package trix - -import ( - "testing" -) - -func FuzzDecode(f *testing.F) { - // Seed with a valid encoded Trix object - validTrix := &Trix{ - Header: map[string]interface{}{"content_type": "text/plain"}, - Payload: []byte("hello world"), - } - validEncoded, _ := Encode(validTrix, "FUZZ") - f.Add(validEncoded) - - // Seed with the corrupted header length from the ugly test - var buf []byte - buf = append(buf, []byte("UGLY")...) - buf = append(buf, byte(Version)) - buf = append(buf, []byte{0, 0, 3, 232}...) // BigEndian representation of 1000 - buf = append(buf, []byte("{}")...) - buf = append(buf, []byte("payload")...) - f.Add(buf) - - // Seed with a short, invalid input - f.Add([]byte("short")) - - f.Fuzz(func(t *testing.T, data []byte) { - // The fuzzer will generate random data here. - // We just need to call our function and make sure it doesn't panic. - // The fuzzer will report any crashes as failures. - _, _ = Decode(data, "FUZZ") - }) -} diff --git a/pkg/trix/trix_test.go b/pkg/trix/trix_test.go deleted file mode 100644 index 5a3cd32..0000000 --- a/pkg/trix/trix_test.go +++ /dev/null @@ -1,202 +0,0 @@ -package trix - -import ( - "io" - "reflect" - "testing" - - "github.com/Snider/Enchantrix/pkg/crypt" - "github.com/stretchr/testify/assert" -) - -// TestTrixEncodeDecode_Good tests the ideal "happy path" scenario for encoding and decoding. -func TestTrixEncodeDecode_Good(t *testing.T) { - header := map[string]interface{}{ - "content_type": "application/octet-stream", - "encryption_algorithm": "chacha20poly1035", - "nonce": "AAECAwQFBgcICQoLDA0ODxAREhMUFRY=", - "created_at": "2025-10-30T12:00:00Z", - } - payload := []byte("This is a secret message.") - trix := &Trix{Header: header, Payload: payload} - magicNumber := "TRIX" - - encoded, err := Encode(trix, magicNumber) - assert.NoError(t, err) - - decoded, err := Decode(encoded, magicNumber) - assert.NoError(t, err) - - assert.True(t, reflect.DeepEqual(trix.Header, decoded.Header)) - assert.Equal(t, trix.Payload, decoded.Payload) -} - -// TestTrixEncodeDecode_Bad tests expected failure scenarios with well-formed but invalid inputs. -func TestTrixEncodeDecode_Bad(t *testing.T) { - t.Run("MismatchedMagicNumber", func(t *testing.T) { - trix := &Trix{Header: map[string]interface{}{}, Payload: []byte("payload")} - encoded, err := Encode(trix, "GOOD") - assert.NoError(t, err) - - _, err = Decode(encoded, "BAD!") - assert.Error(t, err) - assert.Contains(t, err.Error(), "invalid magic number") - }) - - t.Run("InvalidMagicNumberLength", func(t *testing.T) { - trix := &Trix{Header: map[string]interface{}{}, Payload: []byte("payload")} - _, err := Encode(trix, "TOOLONG") - assert.EqualError(t, err, "trix: magic number must be 4 bytes long") - - _, err = Decode([]byte{}, "SHORT") - assert.EqualError(t, err, "trix: magic number must be 4 bytes long") - }) - - t.Run("MalformedHeaderJSON", func(t *testing.T) { - // Create a Trix struct with a header that cannot be marshaled to JSON. - header := map[string]interface{}{ - "unsupported": make(chan int), // Channels cannot be JSON-encoded - } - trix := &Trix{Header: header, Payload: []byte("payload")} - _, err := Encode(trix, "TRIX") - assert.Error(t, err) - assert.Contains(t, err.Error(), "json: unsupported type") - }) -} - -// TestTrixEncodeDecode_Ugly tests malicious or malformed inputs designed to cause crashes or panics. -func TestTrixEncodeDecode_Ugly(t *testing.T) { - magicNumber := "UGLY" - - t.Run("CorruptedHeaderLength", func(t *testing.T) { - // Manually construct a byte slice where the header length is larger than the actual data. - var buf []byte - buf = append(buf, []byte(magicNumber)...) // Magic Number - buf = append(buf, byte(Version)) // Version - // Header length of 1000, but the header is only 2 bytes long. - buf = append(buf, []byte{0, 0, 3, 232}...) // BigEndian representation of 1000 - buf = append(buf, []byte("{}")...) // A minimal valid JSON header - buf = append(buf, []byte("payload")...) - - _, err := Decode(buf, magicNumber) - assert.Error(t, err) - assert.Equal(t, err, io.ErrUnexpectedEOF) - }) - - t.Run("DataTooShort", func(t *testing.T) { - // Data is too short to contain even the magic number. - data := []byte("BAD") - _, err := Decode(data, magicNumber) - assert.Error(t, err) - }) - - t.Run("EmptyPayload", func(t *testing.T) { - data := []byte{} - _, err := Decode(data, magicNumber) - assert.Error(t, err) - }) - - t.Run("FuzzedJSON", func(t *testing.T) { - // A header that is technically valid but contains unexpected types. - header := map[string]interface{}{ - "payload": map[string]interface{}{"nested": 123}, - } - payload := []byte("some data") - trix := &Trix{Header: header, Payload: payload} - - encoded, err := Encode(trix, magicNumber) - assert.NoError(t, err) - - decoded, err := Decode(encoded, magicNumber) - assert.NoError(t, err) - assert.NotNil(t, decoded) - }) -} - -// --- Sigil Tests --- - -func TestPackUnpack_Good(t *testing.T) { - originalPayload := []byte("hello world") - trix := &Trix{ - Header: map[string]interface{}{}, - Payload: originalPayload, - InSigils: []string{"reverse", "reverse"}, // Double reverse should be original - } - - err := trix.Pack() - assert.NoError(t, err) - assert.Equal(t, originalPayload, trix.Payload) // Should be back to the original - - err = trix.Unpack() - assert.NoError(t, err) - assert.Equal(t, originalPayload, trix.Payload) // Should be back to the original again -} - -func TestPackUnpack_Bad(t *testing.T) { - trix := &Trix{ - Header: map[string]interface{}{}, - Payload: []byte("some data"), - InSigils: []string{"reverse", "invalid-sigil-name"}, - } - - err := trix.Pack() - assert.Error(t, err) - assert.Contains(t, err.Error(), "unknown sigil name") -} - -// --- Checksum Tests --- - -func TestChecksum_Good(t *testing.T) { - trix := &Trix{ - Header: map[string]interface{}{}, - Payload: []byte("hello world"), - ChecksumAlgo: crypt.SHA256, - } - encoded, err := Encode(trix, "CHCK") - assert.NoError(t, err) - - decoded, err := Decode(encoded, "CHCK") - assert.NoError(t, err) - assert.Equal(t, trix.Payload, decoded.Payload) -} - -func TestChecksum_Bad(t *testing.T) { - trix := &Trix{ - Header: map[string]interface{}{}, - Payload: []byte("hello world"), - ChecksumAlgo: crypt.SHA256, - } - encoded, err := Encode(trix, "CHCK") - assert.NoError(t, err) - - // Tamper with the payload - encoded[len(encoded)-1] = 0 - - _, err = Decode(encoded, "CHCK") - assert.Error(t, err) - assert.Equal(t, ErrChecksumMismatch, err) -} - -func TestChecksum_Ugly(t *testing.T) { - t.Run("MissingAlgoInHeader", func(t *testing.T) { - trix := &Trix{ - Header: map[string]interface{}{}, - Payload: []byte("hello world"), - ChecksumAlgo: crypt.SHA256, - } - encoded, err := Encode(trix, "UGLY") - assert.NoError(t, err) - - // Manually decode to tamper with the header - decoded, err := Decode(encoded, "UGLY") - assert.NoError(t, err) - delete(decoded.Header, "checksum_algo") - - // Re-encode with the tampered header - tamperedEncoded, err := Encode(decoded, "UGLY") - assert.NoError(t, err) - - _, err = Decode(tamperedEncoded, "UGLY") - assert.Error(t, err) - }) -} diff --git a/tdd/crypt/crypt_test.go b/tdd/crypt/crypt_test.go new file mode 100644 index 0000000..a8e2bff --- /dev/null +++ b/tdd/crypt/crypt_test.go @@ -0,0 +1,169 @@ +package crypt_test + +import ( + "strings" + "testing" + + "github.com/Snider/Enchantrix/pkg/crypt" + "github.com/stretchr/testify/assert" +) + +var service = crypt.NewService() + +// --- Hashing Tests --- + +func TestHash_Good(t *testing.T) { + payload := "hello" + // Test all supported hash types + for _, hashType := range []crypt.HashType{crypt.LTHN, crypt.SHA512, crypt.SHA256, crypt.SHA1, crypt.MD5} { + hash := service.Hash(hashType, payload) + assert.NotEmpty(t, hash, "Hash should not be empty for type %s", hashType) + } +} + +func TestHash_Bad(t *testing.T) { + // Using an unsupported hash type should default to SHA256 + hash := service.Hash("unsupported", "hello") + expectedHash := service.Hash(crypt.SHA256, "hello") + assert.Equal(t, expectedHash, hash) +} + +func TestHash_Ugly(t *testing.T) { + // Test with potentially problematic inputs + testCases := []string{ + "", // Empty string + " ", // Whitespace + "\x00\x01\x02\x03\x04", // Null bytes + strings.Repeat("a", 1024*1024), // Large payload (1MB) + "こんにちは", // Unicode characters + } + + for _, tc := range testCases { + for _, hashType := range []crypt.HashType{crypt.LTHN, crypt.SHA512, crypt.SHA256, crypt.SHA1, crypt.MD5} { + hash := service.Hash(hashType, tc) + assert.NotEmpty(t, hash, "Hash for ugly input should not be empty for type %s", hashType) + } + } +} + +// --- Checksum Tests --- + +// Luhn Tests +func TestLuhn_Good(t *testing.T) { + assert.True(t, service.Luhn("79927398713")) +} + +func TestLuhn_Bad(t *testing.T) { + assert.False(t, service.Luhn("79927398714"), "Should fail for incorrect checksum") + assert.False(t, service.Luhn("7992739871a"), "Should fail for non-numeric input") + assert.False(t, service.Luhn("1"), "Should be false for single digit") +} + +func TestLuhn_Ugly(t *testing.T) { + assert.False(t, service.Luhn(""), "Should be false for empty string") + assert.False(t, service.Luhn(" 1 2 3 "), "Should handle spaces but result in false") + assert.False(t, service.Luhn("!@#$%^&*()"), "Should be false for special characters") +} + +// Fletcher16 Tests +func TestFletcher16_Good(t *testing.T) { + assert.Equal(t, uint16(0xC8F0), service.Fletcher16("abcde")) + assert.Equal(t, uint16(0x2057), service.Fletcher16("abcdef")) + assert.Equal(t, uint16(0x0627), service.Fletcher16("abcdefgh")) +} + +func TestFletcher16_Ugly(t *testing.T) { + assert.Equal(t, uint16(0), service.Fletcher16(""), "Checksum of empty string should be 0") + assert.Equal(t, uint16(0), service.Fletcher16("\x00"), "Checksum of null byte should be 0") +} + +// Fletcher32 Tests +func TestFletcher32_Good(t *testing.T) { + assert.Equal(t, uint32(0xF04FC729), service.Fletcher32("abcde")) + assert.Equal(t, uint32(0x56502D2A), service.Fletcher32("abcdef")) + assert.Equal(t, uint32(0xEBE19591), service.Fletcher32("abcdefgh")) +} + +func TestFletcher32_Ugly(t *testing.T) { + assert.Equal(t, uint32(0), service.Fletcher32(""), "Checksum of empty string should be 0") + // Test odd length string to check padding + assert.NotEqual(t, uint32(0), service.Fletcher32("a"), "Checksum of odd length string") +} + +// Fletcher64 Tests +func TestFletcher64_Good(t *testing.T) { + assert.Equal(t, uint64(0xc8c6c527646362c6), service.Fletcher64("abcde")) + assert.Equal(t, uint64(0xc8c72b276463c8c6), service.Fletcher64("abcdef")) + assert.Equal(t, uint64(0x312e2b28cccac8c6), service.Fletcher64("abcdefgh")) +} + +func TestFletcher64_Ugly(t *testing.T) { + assert.Equal(t, uint64(0), service.Fletcher64(""), "Checksum of empty string should be 0") + // Test different length strings to check padding + assert.NotEqual(t, uint64(0), service.Fletcher64("a"), "Checksum of length 1 string") + assert.NotEqual(t, uint64(0), service.Fletcher64("ab"), "Checksum of length 2 string") + assert.NotEqual(t, uint64(0), service.Fletcher64("abc"), "Checksum of length 3 string") +} + + +// --- RSA Tests --- + +func TestRSA_Good(t *testing.T) { + pubKey, privKey, err := service.GenerateRSAKeyPair(2048) + assert.NoError(t, err) + assert.NotNil(t, pubKey) + assert.NotNil(t, privKey) + + // Test encryption and decryption + message := []byte("secret message") + label := []byte("test label") + ciphertext, err := service.EncryptRSA(pubKey, message, label) + assert.NoError(t, err) + plaintext, err := service.DecryptRSA(privKey, ciphertext, label) + assert.NoError(t, err) + assert.Equal(t, message, plaintext) +} + +func TestRSA_Bad(t *testing.T) { + // Test with a key size that is too small + _, _, err := service.GenerateRSAKeyPair(1024) + assert.Error(t, err) + + // Test decryption with the wrong key + pubKey, privKey, err := service.GenerateRSAKeyPair(2048) + assert.NoError(t, err) + _, otherPrivKey, err := service.GenerateRSAKeyPair(2048) + assert.NoError(t, err) + message := []byte("secret message") + ciphertext, err := service.EncryptRSA(pubKey, message, nil) + assert.NoError(t, err) + _, err = service.DecryptRSA(otherPrivKey, ciphertext, nil) + assert.Error(t, err) + + // Test decryption with wrong label + label1 := []byte("label1") + label2 := []byte("label2") + ciphertext, err = service.EncryptRSA(pubKey, message, label1) + assert.NoError(t, err) + _, err = service.DecryptRSA(privKey, ciphertext, label2) + assert.Error(t, err) +} + +func TestRSA_Ugly(t *testing.T) { + // Test with malformed keys + _, err := service.EncryptRSA([]byte("not a real key"), []byte("message"), nil) + assert.Error(t, err) + + _, err = service.DecryptRSA([]byte("not a real key"), []byte("message"), nil) + assert.Error(t, err) + + // Test with empty message + pubKey, privKey, err := service.GenerateRSAKeyPair(2048) + assert.NoError(t, err) + message := []byte("") + ciphertext, err := service.EncryptRSA(pubKey, message, nil) + assert.NoError(t, err) + plaintext, err := service.DecryptRSA(privKey, ciphertext, nil) + assert.NoError(t, err) + assert.Equal(t, message, plaintext) +} diff --git a/tdd/enchantrix/enchantrix_test.go b/tdd/enchantrix/enchantrix_test.go new file mode 100644 index 0000000..79e6330 --- /dev/null +++ b/tdd/enchantrix/enchantrix_test.go @@ -0,0 +1,159 @@ +package enchantrix_test + +import ( + "crypto" + "encoding/hex" + "errors" + "testing" + + "github.com/Snider/Enchantrix/pkg/enchantrix" + "github.com/stretchr/testify/assert" +) + +// --- Transmute Tests --- + +func TestTransmute_Good(t *testing.T) { + data := []byte("hello") + sigils := []enchantrix.Sigil{ + &enchantrix.ReverseSigil{}, + &enchantrix.HexSigil{}, + } + result, err := enchantrix.Transmute(data, sigils) + assert.NoError(t, err) + assert.Equal(t, "6f6c6c6568", string(result)) +} + +type errorSigil struct{} + +func (s *errorSigil) In(data []byte) ([]byte, error) { + return nil, errors.New("sigil error") +} +func (s *errorSigil) Out(data []byte) ([]byte, error) { + return nil, errors.New("sigil error") +} + +func TestTransmute_Bad(t *testing.T) { + data := []byte("hello") + sigils := []enchantrix.Sigil{ + &enchantrix.ReverseSigil{}, + &errorSigil{}, + } + _, err := enchantrix.Transmute(data, sigils) + assert.Error(t, err) +} + +// --- Factory Tests --- + +func TestNewSigil_Good(t *testing.T) { + validNames := []string{ + "reverse", "hex", "base64", "gzip", "json", "json-indent", + "md4", "md5", "sha1", "sha224", "sha256", "sha384", "sha512", + "ripemd160", "sha3-224", "sha3-256", "sha3-384", "sha3-512", + "sha512-224", "sha512-256", "blake2s-256", "blake2b-256", + "blake2b-384", "blake2b-512", + } + for _, name := range validNames { + sigil, err := enchantrix.NewSigil(name) + assert.NoError(t, err, "Failed to create sigil: %s", name) + assert.NotNil(t, sigil, "Sigil should not be nil for name: %s", name) + } +} + +func TestNewSigil_Bad(t *testing.T) { + sigil, err := enchantrix.NewSigil("invalid-sigil-name") + assert.Error(t, err) + assert.Nil(t, sigil) + assert.Contains(t, err.Error(), "unknown sigil name") +} + +// --- Sigil Tests --- + +func TestReverseSigil(t *testing.T) { + s := &enchantrix.ReverseSigil{} + data := []byte("hello") + reversed, err := s.In(data) + assert.NoError(t, err) + assert.Equal(t, "olleh", string(reversed)) + original, err := s.Out(reversed) + assert.NoError(t, err) + assert.Equal(t, "hello", string(original)) + + // Ugly - empty string + empty := []byte("") + reversedEmpty, err := s.In(empty) + assert.NoError(t, err) + assert.Equal(t, "", string(reversedEmpty)) +} + +func TestHexSigil(t *testing.T) { + s := &enchantrix.HexSigil{} + data := []byte("hello") + encoded, err := s.In(data) + assert.NoError(t, err) + assert.Equal(t, "68656c6c6f", string(encoded)) + decoded, err := s.Out(encoded) + assert.NoError(t, err) + assert.Equal(t, "hello", string(decoded)) + + // Bad - invalid hex string + _, err = s.Out([]byte("not hex")) + assert.Error(t, err) +} + +func TestBase64Sigil(t *testing.T) { + s := &enchantrix.Base64Sigil{} + data := []byte("hello") + encoded, err := s.In(data) + assert.NoError(t, err) + assert.Equal(t, "aGVsbG8=", string(encoded)) + decoded, err := s.Out(encoded) + assert.NoError(t, err) + assert.Equal(t, "hello", string(decoded)) + + // Bad - invalid base64 string + _, err = s.Out([]byte("not base64")) + assert.Error(t, err) +} + +func TestGzipSigil(t *testing.T) { + s := &enchantrix.GzipSigil{} + data := []byte("hello") + compressed, err := s.In(data) + assert.NoError(t, err) + assert.NotEqual(t, data, compressed) + decompressed, err := s.Out(compressed) + assert.NoError(t, err) + assert.Equal(t, "hello", string(decompressed)) + + // Bad - invalid gzip data + _, err = s.Out([]byte("not gzip")) + assert.Error(t, err) +} + +func TestJSONSigil(t *testing.T) { + s := &enchantrix.JSONSigil{Indent: true} + data := []byte(`{"hello":"world"}`) + indented, err := s.In(data) + assert.NoError(t, err) + assert.Equal(t, "{\n \"hello\": \"world\"\n}", string(indented)) + s.Indent = false + compacted, err := s.In(indented) + assert.NoError(t, err) + assert.Equal(t, `{"hello":"world"}`, string(compacted)) + + // Bad - invalid json + _, err = s.In([]byte("not json")) + assert.Error(t, err) +} + +func TestHashSigil(t *testing.T) { + s := enchantrix.NewHashSigil(crypto.SHA256) + data := []byte("hello") + hashed, err := s.In(data) + assert.NoError(t, err) + expectedHash := "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" + assert.Equal(t, expectedHash, hex.EncodeToString(hashed)) + unhashed, err := s.Out(hashed) + assert.NoError(t, err) + assert.Equal(t, hashed, unhashed) // Out is a no-op +} diff --git a/tdd/trix/trix_test.go b/tdd/trix/trix_test.go new file mode 100644 index 0000000..d0a9cec --- /dev/null +++ b/tdd/trix/trix_test.go @@ -0,0 +1,252 @@ +package trix_test + +import ( + "io" + "reflect" + "testing" + + "github.com/Snider/Enchantrix/pkg/crypt" + "github.com/Snider/Enchantrix/pkg/trix" + "github.com/stretchr/testify/assert" +) + +// TestTrixEncodeDecode_Good tests the ideal "happy path" scenario for encoding and decoding. +func TestTrixEncodeDecode_Good(t *testing.T) { + header := map[string]interface{}{ + "content_type": "application/octet-stream", + "encryption_algorithm": "chacha20poly1035", + "nonce": "AAECAwQFBgcICQoLDA0ODxAREhMUFRY=", + "created_at": "2025-10-30T12:00:00Z", + } + payload := []byte("This is a secret message.") + trixOb := &trix.Trix{Header: header, Payload: payload} + magicNumber := "TRIX" + + encoded, err := trix.Encode(trixOb, magicNumber) + assert.NoError(t, err) + + decoded, err := trix.Decode(encoded, magicNumber) + assert.NoError(t, err) + + assert.True(t, reflect.DeepEqual(trixOb.Header, decoded.Header)) + assert.Equal(t, trixOb.Payload, decoded.Payload) +} + +// TestTrixEncodeDecode_Bad tests expected failure scenarios with well-formed but invalid inputs. +func TestTrixEncodeDecode_Bad(t *testing.T) { + t.Run("MismatchedMagicNumber", func(t *testing.T) { + trixOb := &trix.Trix{Header: map[string]interface{}{}, Payload: []byte("payload")} + encoded, err := trix.Encode(trixOb, "GOOD") + assert.NoError(t, err) + + _, err = trix.Decode(encoded, "BAD!") + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid magic number") + }) + + t.Run("InvalidMagicNumberLength", func(t *testing.T) { + trixOb := &trix.Trix{Header: map[string]interface{}{}, Payload: []byte("payload")} + _, err := trix.Encode(trixOb, "TOOLONG") + assert.EqualError(t, err, "trix: magic number must be 4 bytes long") + + _, err = trix.Decode([]byte{}, "SHORT") + assert.EqualError(t, err, "trix: magic number must be 4 bytes long") + }) + + t.Run("MalformedHeaderJSON", func(t *testing.T) { + // Create a Trix struct with a header that cannot be marshaled to JSON. + header := map[string]interface{}{ + "unsupported": make(chan int), // Channels cannot be JSON-encoded + } + trixOb := &trix.Trix{Header: header, Payload: []byte("payload")} + _, err := trix.Encode(trixOb, "TRIX") + assert.Error(t, err) + assert.Contains(t, err.Error(), "json: unsupported type") + }) + + t.Run("HeaderTooLarge", func(t *testing.T) { + data := make([]byte, trix.MaxHeaderSize+10) + trixOb := &trix.Trix{ + Header: map[string]interface{}{"large": string(data)}, + Payload: []byte("payload"), + } + encoded, err := trix.Encode(trixOb, "TRIX") + assert.NoError(t, err) + + _, err = trix.Decode(encoded, "TRIX") + assert.ErrorIs(t, err, trix.ErrHeaderTooLarge) + }) +} + +// TestTrixEncodeDecode_Ugly tests malicious or malformed inputs designed to cause crashes or panics. +func TestTrixEncodeDecode_Ugly(t *testing.T) { + magicNumber := "UGLY" + + t.Run("CorruptedHeaderLength", func(t *testing.T) { + // Manually construct a byte slice where the header length is larger than the actual data. + var buf []byte + buf = append(buf, []byte(magicNumber)...) // Magic Number + buf = append(buf, byte(trix.Version)) // Version + buf = append(buf, []byte{0, 0, 3, 232}...) // BigEndian representation of 1000 + buf = append(buf, []byte("{}")...) // A minimal valid JSON header + buf = append(buf, []byte("payload")...) + + _, err := trix.Decode(buf, magicNumber) + assert.Error(t, err) + assert.Equal(t, err, io.ErrUnexpectedEOF) + }) + + t.Run("DataTooShort", func(t *testing.T) { + data := []byte("BAD") + _, err := trix.Decode(data, magicNumber) + assert.Error(t, err) + }) + + t.Run("EmptyPayload", func(t *testing.T) { + data := []byte{} + _, err := trix.Decode(data, magicNumber) + assert.Error(t, err) + }) + + t.Run("FuzzedJSON", func(t *testing.T) { + header := map[string]interface{}{ + "payload": map[string]interface{}{"nested": 123}, + } + payload := []byte("some data") + trixOb := &trix.Trix{Header: header, Payload: payload} + + encoded, err := trix.Encode(trixOb, magicNumber) + assert.NoError(t, err) + + decoded, err := trix.Decode(encoded, magicNumber) + assert.NoError(t, err) + assert.NotNil(t, decoded) + }) +} + +// --- Sigil Tests --- + +func TestPackUnpack_Good(t *testing.T) { + originalPayload := []byte("hello world") + trixOb := &trix.Trix{ + Header: map[string]interface{}{}, + Payload: originalPayload, + InSigils: []string{"reverse", "reverse"}, // Double reverse should be original + } + + err := trixOb.Pack() + assert.NoError(t, err) + assert.Equal(t, originalPayload, trixOb.Payload) + + err = trixOb.Unpack() + assert.NoError(t, err) + assert.Equal(t, originalPayload, trixOb.Payload) +} + +func TestPackUnpack_Bad(t *testing.T) { + trixOb := &trix.Trix{ + Header: map[string]interface{}{}, + Payload: []byte("some data"), + InSigils: []string{"reverse", "invalid-sigil-name"}, + } + + err := trixOb.Pack() + assert.Error(t, err) + assert.Contains(t, err.Error(), "unknown sigil name") + + trixOb.InSigils = []string{"hex"} + trixOb.Payload = []byte("not hex") + err = trixOb.Unpack() + assert.Error(t, err) +} + +func TestPackUnpack_Ugly(t *testing.T) { + trixOb := &trix.Trix{ + Header: map[string]interface{}{}, + Payload: nil, // Nil payload + InSigils: []string{"reverse"}, + } + err := trixOb.Pack() + assert.NoError(t, err) // Should handle nil payload gracefully + + err = trixOb.Unpack() + assert.NoError(t, err) +} + +// --- Checksum Tests --- + +func TestChecksum_Good(t *testing.T) { + trixOb := &trix.Trix{ + Header: map[string]interface{}{}, + Payload: []byte("hello world"), + ChecksumAlgo: crypt.SHA256, + } + encoded, err := trix.Encode(trixOb, "CHCK") + assert.NoError(t, err) + + decoded, err := trix.Decode(encoded, "CHCK") + assert.NoError(t, err) + assert.Equal(t, trixOb.Payload, decoded.Payload) +} + +func TestChecksum_Bad(t *testing.T) { + trixOb := &trix.Trix{ + Header: map[string]interface{}{}, + Payload: []byte("hello world"), + ChecksumAlgo: crypt.SHA256, + } + encoded, err := trix.Encode(trixOb, "CHCK") + assert.NoError(t, err) + + encoded[len(encoded)-1] = 0 // Tamper with the payload + + _, err = trix.Decode(encoded, "CHCK") + assert.ErrorIs(t, err, trix.ErrChecksumMismatch) +} + +func TestChecksum_Ugly(t *testing.T) { + t.Run("MissingAlgoInHeader", func(t *testing.T) { + trixOb := &trix.Trix{ + Header: map[string]interface{}{}, + Payload: []byte("hello world"), + ChecksumAlgo: crypt.SHA256, + } + encoded, err := trix.Encode(trixOb, "UGLY") + assert.NoError(t, err) + + decoded, err := trix.Decode(encoded, "UGLY") + assert.NoError(t, err) + delete(decoded.Header, "checksum_algo") + + tamperedEncoded, err := trix.Encode(decoded, "UGLY") + assert.NoError(t, err) + + _, err = trix.Decode(tamperedEncoded, "UGLY") + assert.Error(t, err) + }) +} + +// --- Fuzz Tests --- + +func FuzzDecode(f *testing.F) { + validTrix := &trix.Trix{ + Header: map[string]interface{}{"content_type": "text/plain"}, + Payload: []byte("hello world"), + } + validEncoded, _ := trix.Encode(validTrix, "FUZZ") + f.Add(validEncoded) + + var buf []byte + buf = append(buf, []byte("UGLY")...) + buf = append(buf, byte(trix.Version)) + buf = append(buf, []byte{0, 0, 3, 232}...) + buf = append(buf, []byte("{}")...) + buf = append(buf, []byte("payload")...) + f.Add(buf) + + f.Add([]byte("short")) + + f.Fuzz(func(t *testing.T, data []byte) { + _, _ = trix.Decode(data, "FUZZ") + }) +}