Implements ChaChaPolySigil that applies pre-obfuscation before sending data to CPU encryption routines. This ensures raw plaintext is never passed directly to encryption functions. Key improvements: - XORObfuscator and ShuffleMaskObfuscator for pre-encryption transforms - Nonce is now properly embedded in ciphertext, not stored separately in headers (production-ready, not demo-style) - Trix crypto integration with EncryptPayload/DecryptPayload methods - Comprehensive test coverage following Good/Bad/Ugly pattern
189 lines
5.3 KiB
Go
189 lines
5.3 KiB
Go
package trix
|
|
|
|
import (
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/Snider/Enchantrix/pkg/enchantrix"
|
|
)
|
|
|
|
var (
|
|
// ErrNoEncryptionKey is returned when encryption is requested without a key.
|
|
ErrNoEncryptionKey = errors.New("trix: encryption key not configured")
|
|
// ErrAlreadyEncrypted is returned when trying to encrypt already encrypted data.
|
|
ErrAlreadyEncrypted = errors.New("trix: payload is already encrypted")
|
|
// ErrNotEncrypted is returned when trying to decrypt non-encrypted data.
|
|
ErrNotEncrypted = errors.New("trix: payload is not encrypted")
|
|
)
|
|
|
|
const (
|
|
// HeaderKeyEncrypted indicates whether the payload is encrypted.
|
|
HeaderKeyEncrypted = "encrypted"
|
|
// HeaderKeyAlgorithm stores the encryption algorithm used.
|
|
HeaderKeyAlgorithm = "encryption_algorithm"
|
|
// HeaderKeyEncryptedAt stores when the payload was encrypted.
|
|
HeaderKeyEncryptedAt = "encrypted_at"
|
|
// HeaderKeyObfuscator stores the obfuscator type used.
|
|
HeaderKeyObfuscator = "obfuscator"
|
|
|
|
// AlgorithmChaCha20Poly1305 is the identifier for ChaCha20-Poly1305.
|
|
AlgorithmChaCha20Poly1305 = "xchacha20-poly1305"
|
|
// ObfuscatorXOR identifies the XOR obfuscator.
|
|
ObfuscatorXOR = "xor"
|
|
// ObfuscatorShuffleMask identifies the shuffle-mask obfuscator.
|
|
ObfuscatorShuffleMask = "shuffle-mask"
|
|
)
|
|
|
|
// CryptoConfig holds encryption configuration for a Trix container.
|
|
type CryptoConfig struct {
|
|
// Key is the 32-byte encryption key.
|
|
Key []byte
|
|
// Obfuscator type: "xor" (default) or "shuffle-mask"
|
|
Obfuscator string
|
|
}
|
|
|
|
// EncryptPayload encrypts the Trix payload using ChaCha20-Poly1305 with pre-obfuscation.
|
|
//
|
|
// The nonce is embedded in the ciphertext itself and is NOT stored separately
|
|
// in the header. This is the production-ready approach (not demo-style).
|
|
//
|
|
// Header metadata is updated to indicate encryption status without exposing
|
|
// cryptographic parameters that are already embedded in the ciphertext.
|
|
func (t *Trix) EncryptPayload(config *CryptoConfig) error {
|
|
if config == nil || len(config.Key) != 32 {
|
|
return ErrNoEncryptionKey
|
|
}
|
|
|
|
// Check if already encrypted
|
|
if encrypted, ok := t.Header[HeaderKeyEncrypted].(bool); ok && encrypted {
|
|
return ErrAlreadyEncrypted
|
|
}
|
|
|
|
// Create the obfuscator
|
|
var obfuscator enchantrix.PreObfuscator
|
|
obfuscatorName := ObfuscatorXOR
|
|
switch config.Obfuscator {
|
|
case ObfuscatorShuffleMask:
|
|
obfuscator = &enchantrix.ShuffleMaskObfuscator{}
|
|
obfuscatorName = ObfuscatorShuffleMask
|
|
default:
|
|
obfuscator = &enchantrix.XORObfuscator{}
|
|
}
|
|
|
|
// Create the encryption sigil
|
|
sigil, err := enchantrix.NewChaChaPolySigilWithObfuscator(config.Key, obfuscator)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Encrypt the payload
|
|
ciphertext, err := sigil.In(t.Payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update payload with ciphertext
|
|
t.Payload = ciphertext
|
|
|
|
// Update header with encryption metadata
|
|
// NOTE: We do NOT store the nonce in the header - it's embedded in the ciphertext
|
|
if t.Header == nil {
|
|
t.Header = make(map[string]interface{})
|
|
}
|
|
t.Header[HeaderKeyEncrypted] = true
|
|
t.Header[HeaderKeyAlgorithm] = AlgorithmChaCha20Poly1305
|
|
t.Header[HeaderKeyObfuscator] = obfuscatorName
|
|
t.Header[HeaderKeyEncryptedAt] = time.Now().UTC().Format(time.RFC3339)
|
|
|
|
return nil
|
|
}
|
|
|
|
// DecryptPayload decrypts the Trix payload using the provided key.
|
|
//
|
|
// The nonce is extracted from the ciphertext itself - no need to read it
|
|
// from the header separately.
|
|
func (t *Trix) DecryptPayload(config *CryptoConfig) error {
|
|
if config == nil || len(config.Key) != 32 {
|
|
return ErrNoEncryptionKey
|
|
}
|
|
|
|
// Check if encrypted
|
|
encrypted, ok := t.Header[HeaderKeyEncrypted].(bool)
|
|
if !ok || !encrypted {
|
|
return ErrNotEncrypted
|
|
}
|
|
|
|
// Determine obfuscator from header
|
|
var obfuscator enchantrix.PreObfuscator
|
|
if obfType, ok := t.Header[HeaderKeyObfuscator].(string); ok {
|
|
switch obfType {
|
|
case ObfuscatorShuffleMask:
|
|
obfuscator = &enchantrix.ShuffleMaskObfuscator{}
|
|
default:
|
|
obfuscator = &enchantrix.XORObfuscator{}
|
|
}
|
|
} else {
|
|
obfuscator = &enchantrix.XORObfuscator{}
|
|
}
|
|
|
|
// Create the decryption sigil
|
|
sigil, err := enchantrix.NewChaChaPolySigilWithObfuscator(config.Key, obfuscator)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Decrypt the payload
|
|
plaintext, err := sigil.Out(t.Payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update payload with plaintext
|
|
t.Payload = plaintext
|
|
|
|
// Update header to indicate decrypted state
|
|
t.Header[HeaderKeyEncrypted] = false
|
|
|
|
return nil
|
|
}
|
|
|
|
// IsEncrypted returns true if the payload is currently encrypted.
|
|
func (t *Trix) IsEncrypted() bool {
|
|
if t.Header == nil {
|
|
return false
|
|
}
|
|
encrypted, ok := t.Header[HeaderKeyEncrypted].(bool)
|
|
return ok && encrypted
|
|
}
|
|
|
|
// GetEncryptionAlgorithm returns the encryption algorithm used, if any.
|
|
func (t *Trix) GetEncryptionAlgorithm() string {
|
|
if t.Header == nil {
|
|
return ""
|
|
}
|
|
algo, ok := t.Header[HeaderKeyAlgorithm].(string)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
return algo
|
|
}
|
|
|
|
// NewEncryptedTrix creates a new Trix container with an encrypted payload.
|
|
// This is a convenience function for creating encrypted containers in one step.
|
|
func NewEncryptedTrix(payload []byte, key []byte, header map[string]interface{}) (*Trix, error) {
|
|
if header == nil {
|
|
header = make(map[string]interface{})
|
|
}
|
|
|
|
t := &Trix{
|
|
Header: header,
|
|
Payload: payload,
|
|
}
|
|
|
|
config := &CryptoConfig{Key: key}
|
|
if err := t.EncryptPayload(config); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return t, nil
|
|
}
|