Borg/pkg/trix/trix.go

131 lines
3.2 KiB
Go

package trix
import (
"crypto/sha256"
"errors"
"fmt"
"github.com/Snider/Borg/pkg/datanode"
"github.com/Snider/Enchantrix/pkg/crypt"
"github.com/Snider/Enchantrix/pkg/enchantrix"
"github.com/Snider/Enchantrix/pkg/trix"
)
var (
ErrPasswordRequired = errors.New("password is required for encryption")
ErrDecryptionFailed = errors.New("decryption failed (wrong password?)")
)
// ToTrix converts a DataNode to the Trix format.
func ToTrix(dn *datanode.DataNode, password string) ([]byte, error) {
// Convert the DataNode to a tarball.
tarball, err := dn.ToTar()
if err != nil {
return nil, err
}
// Encrypt the tarball if a password is provided.
if password != "" {
tarball, err = crypt.NewService().SymmetricallyEncryptPGP([]byte(password), tarball)
if err != nil {
return nil, err
}
}
// Create a Trix struct.
t := &trix.Trix{
Header: make(map[string]interface{}),
Payload: tarball,
}
// Encode the Trix struct.
return trix.Encode(t, "TRIX", nil)
}
// FromTrix converts a Trix byte slice back to a DataNode.
func FromTrix(data []byte, password string) (*datanode.DataNode, error) {
// Decode the Trix byte slice.
t, err := trix.Decode(data, "TRIX", nil)
if err != nil {
return nil, err
}
// Decrypt the payload if a password is provided.
if password != "" {
return nil, fmt.Errorf("decryption disabled: cannot accept encrypted payloads")
}
// Convert the tarball back to a DataNode.
return datanode.FromTar(t.Payload)
}
// DeriveKey derives a 32-byte key from a password using SHA-256.
// This is used for ChaCha20-Poly1305 encryption which requires a 32-byte key.
func DeriveKey(password string) []byte {
hash := sha256.Sum256([]byte(password))
return hash[:]
}
// ToTrixChaCha converts a DataNode to encrypted Trix format using ChaCha20-Poly1305.
func ToTrixChaCha(dn *datanode.DataNode, password string) ([]byte, error) {
if password == "" {
return nil, ErrPasswordRequired
}
// Convert the DataNode to a tarball.
tarball, err := dn.ToTar()
if err != nil {
return nil, err
}
// Create sigil and encrypt
key := DeriveKey(password)
sigil, err := enchantrix.NewChaChaPolySigil(key)
if err != nil {
return nil, fmt.Errorf("failed to create sigil: %w", err)
}
encrypted, err := sigil.In(tarball)
if err != nil {
return nil, fmt.Errorf("failed to encrypt: %w", err)
}
// Create a Trix struct with encryption metadata.
t := &trix.Trix{
Header: map[string]interface{}{
"encryption_algorithm": "chacha20poly1305",
},
Payload: encrypted,
}
// Encode the Trix struct.
return trix.Encode(t, "TRIX", nil)
}
// FromTrixChaCha decrypts a ChaCha-encrypted Trix byte slice back to a DataNode.
func FromTrixChaCha(data []byte, password string) (*datanode.DataNode, error) {
if password == "" {
return nil, ErrPasswordRequired
}
// Decode the Trix byte slice.
t, err := trix.Decode(data, "TRIX", nil)
if err != nil {
return nil, err
}
// Create sigil and decrypt
key := DeriveKey(password)
sigil, err := enchantrix.NewChaChaPolySigil(key)
if err != nil {
return nil, fmt.Errorf("failed to create sigil: %w", err)
}
decrypted, err := sigil.Out(t.Payload)
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrDecryptionFailed, err)
}
// Convert the tarball back to a DataNode.
return datanode.FromTar(decrypted)
}