diff --git a/examples/main.go b/examples/main.go index 0a31fb2..8f17c16 100644 --- a/examples/main.go +++ b/examples/main.go @@ -11,84 +11,78 @@ import ( ) func main() { - // 1. Original plaintext + // 1. Original plaintext and encryption key plaintext := []byte("This is a super secret message!") key := make([]byte, 32) // In a real application, use a secure key for i := range key { key[i] = 1 } - // 2. Encrypt the data using the chachapoly package - // The ciphertext from chachapoly includes the nonce. - ciphertext, err := chachapoly.Encrypt(plaintext, key) + // 2. Create a Trix container with the plaintext and attach sigils + trixContainer := &trix.Trix{ + Header: map[string]interface{}{}, + Payload: plaintext, + Sigils: []trix.Sigil{&trix.ReverseSigil{}}, + } + + // 3. Pack the Trix container to apply the sigil transformations + if err := trixContainer.Pack(); err != nil { + log.Fatalf("Failed to pack trix container: %v", err) + } + fmt.Printf("Packed (obfuscated) payload: %x\n", trixContainer.Payload) + + + // 4. Encrypt the packed payload + ciphertext, err := chachapoly.Encrypt(trixContainer.Payload, key) if err != nil { log.Fatalf("Failed to encrypt: %v", err) } + trixContainer.Payload = ciphertext // Update the payload with the ciphertext - // For the .trix header, we need to separate the nonce from the ciphertext. - // chacha20poly1305.NewX nonce size is 24 bytes. + // 5. Add encryption metadata to the header nonce := ciphertext[:24] - actualCiphertext := ciphertext[24:] - - // 3. Create a .trix container for the encrypted data - header := map[string]interface{}{ + trixContainer.Header = map[string]interface{}{ "content_type": "application/octet-stream", "encryption_algorithm": "chacha20poly1305", "nonce": base64.StdEncoding.EncodeToString(nonce), "created_at": time.Now().UTC().Format(time.RFC3339), } - trixContainer := &trix.Trix{ - Header: header, - Payload: actualCiphertext, - Sigils: []trix.Sigil{&trix.ReverseSigil{}}, - } - // 4. Encode the .trix container into its binary format - magicNumber := "MyT1" // My Trix 1 + // 6. Encode the .trix container into its binary format + magicNumber := "MyT1" encodedTrix, err := trix.Encode(trixContainer, magicNumber) if err != nil { log.Fatalf("Failed to encode .trix container: %v", err) } - fmt.Println("Successfully created .trix container.") - // 5. Decode the .trix container to retrieve the encrypted data + // --- DECODING --- + + // 7. Decode the .trix container decodedTrix, err := trix.Decode(encodedTrix, magicNumber) if err != nil { log.Fatalf("Failed to decode .trix container: %v", err) } - // Manually apply the Out method of the sigil to restore the original payload. - restoredPayload, err := trixContainer.Sigils[0].Out(decodedTrix.Payload) - if err != nil { - log.Fatalf("Failed to apply sigil: %v", err) - } - decodedTrix.Payload = restoredPayload - - // 6. Reassemble the ciphertext (nonce + payload) and decrypt - retrievedNonceStr, ok := decodedTrix.Header["nonce"].(string) - if !ok { - log.Fatalf("Nonce not found or not a string in header") - } - retrievedNonce, err := base64.StdEncoding.DecodeString(retrievedNonceStr) - if err != nil { - log.Fatalf("Failed to decode nonce: %v", err) - } - retrievedCiphertext := append(retrievedNonce, decodedTrix.Payload...) - - decrypted, err := chachapoly.Decrypt(retrievedCiphertext, key) + // 8. Decrypt the payload + decryptedPayload, err := chachapoly.Decrypt(decodedTrix.Payload, key) if err != nil { log.Fatalf("Failed to decrypt: %v", err) } + decodedTrix.Payload = decryptedPayload - // 7. Verify the result - fmt.Printf("Original plaintext: %s\n", plaintext) - fmt.Printf("Decrypted plaintext: %s\n", decrypted) + // 9. Unpack the Trix container to reverse the sigil transformations + decodedTrix.Sigils = trixContainer.Sigils // Re-attach sigils + if err := decodedTrix.Unpack(); err != nil { + log.Fatalf("Failed to unpack trix container: %v", err) + } + fmt.Printf("Unpacked (original) payload: %s\n", decodedTrix.Payload) - if string(plaintext) == string(decrypted) { - fmt.Println("\nSuccess! The message was decrypted correctly.") + // 10. Verify the result + if string(plaintext) == string(decodedTrix.Payload) { + fmt.Println("\nSuccess! The message was decrypted and unpacked correctly.") } else { - fmt.Println("\nFailure! The decrypted message does not match the original.") + fmt.Println("\nFailure! The final payload does not match the original.") } } diff --git a/pkg/trix/trix.go b/pkg/trix/trix.go index bab5edd..b2b5dcd 100644 --- a/pkg/trix/trix.go +++ b/pkg/trix/trix.go @@ -39,19 +39,6 @@ func Encode(trix *Trix, magicNumber string) ([]byte, error) { return nil, ErrMagicNumberLength } - // Apply sigils to the payload before encoding - payload := trix.Payload - for _, sigil := range trix.Sigils { - if sigil == nil { - return nil, ErrNilSigil - } - var err error - payload, err = sigil.In(payload) - if err != nil { - return nil, err - } - } - headerBytes, err := json.Marshal(trix.Header) if err != nil { return nil, err @@ -81,7 +68,7 @@ func Encode(trix *Trix, magicNumber string) ([]byte, error) { } // Write Payload - if _, err := buf.Write(payload); err != nil { + if _, err := buf.Write(trix.Payload); err != nil { return nil, err } @@ -143,6 +130,37 @@ func Decode(data []byte, magicNumber string) (*Trix, error) { }, nil } +// Pack applies the In method of all attached sigils to the payload. +func (t *Trix) Pack() error { + for _, sigil := range t.Sigils { + if sigil == nil { + return ErrNilSigil + } + var err error + t.Payload, err = sigil.In(t.Payload) + if err != nil { + return err + } + } + return nil +} + +// Unpack applies the Out method of all sigils in reverse order. +func (t *Trix) Unpack() error { + for i := len(t.Sigils) - 1; i >= 0; i-- { + sigil := t.Sigils[i] + if sigil == nil { + return ErrNilSigil + } + var err error + t.Payload, err = sigil.Out(t.Payload) + if err != nil { + return err + } + } + return nil +} + // ReverseSigil is an example Sigil that reverses the bytes of the payload. type ReverseSigil struct{} diff --git a/pkg/trix/trix_test.go b/pkg/trix/trix_test.go index 2cf4912..a9acb9f 100644 --- a/pkg/trix/trix_test.go +++ b/pkg/trix/trix_test.go @@ -127,27 +127,24 @@ func (s *FailingSigil) Out(data []byte) ([]byte, error) { return nil, s.err } -func TestSigilPipeline_Good(t *testing.T) { +func TestPackUnpack_Good(t *testing.T) { originalPayload := []byte("hello world") trix := &Trix{ Header: map[string]interface{}{}, Payload: originalPayload, - Sigils: []Sigil{&ReverseSigil{}}, + Sigils: []Sigil{&ReverseSigil{}, &ReverseSigil{}}, // Double reverse should be original } - encoded, err := Encode(trix, "SIGL") + err := trix.Pack() assert.NoError(t, err) + assert.Equal(t, originalPayload, trix.Payload) // Should be back to the original - decoded, err := Decode(encoded, "SIGL") + err = trix.Unpack() assert.NoError(t, err) - - // Manually apply the Out method to restore the original payload. - restoredPayload, err := trix.Sigils[0].Out(decoded.Payload) - assert.NoError(t, err) - assert.Equal(t, originalPayload, restoredPayload) + assert.Equal(t, originalPayload, trix.Payload) // Should be back to the original again } -func TestSigilPipeline_Bad(t *testing.T) { +func TestPackUnpack_Bad(t *testing.T) { expectedErr := errors.New("sigil failed") trix := &Trix{ Header: map[string]interface{}{}, @@ -155,12 +152,12 @@ func TestSigilPipeline_Bad(t *testing.T) { Sigils: []Sigil{&ReverseSigil{}, &FailingSigil{err: expectedErr}}, } - _, err := Encode(trix, "FAIL") + err := trix.Pack() assert.Error(t, err) assert.Equal(t, expectedErr, err) } -func TestSigilPipeline_Ugly(t *testing.T) { +func TestPackUnpack_Ugly(t *testing.T) { t.Run("NilSigil", func(t *testing.T) { trix := &Trix{ Header: map[string]interface{}{}, @@ -168,7 +165,7 @@ func TestSigilPipeline_Ugly(t *testing.T) { Sigils: []Sigil{nil}, } - _, err := Encode(trix, "UGLY") + err := trix.Pack() assert.Error(t, err) assert.Equal(t, ErrNilSigil, err) })