179 lines
4.7 KiB
Go
179 lines
4.7 KiB
Go
/*
|
|
©AngelaMos | 2026
|
|
envelope_test.go
|
|
|
|
Round-trip, integrity, and hardening tests for the payload envelope
|
|
*/
|
|
|
|
package payload
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
var secret = []byte("the eagle lands at midnight -- coordinates 41.40N 2.17E")
|
|
|
|
func TestRoundTrip(t *testing.T) {
|
|
pass := []byte("correct horse battery staple")
|
|
cases := []struct {
|
|
name string
|
|
opts Options
|
|
}{
|
|
{"plain", Options{}},
|
|
{"compressed", Options{Compress: true}},
|
|
{"encrypted-chacha", Options{Passphrase: pass}},
|
|
{"encrypted-aes", Options{Passphrase: pass, Cipher: CipherAES256GCM}},
|
|
{"encrypted-compressed", Options{Passphrase: pass, Compress: true}},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
env, err := Pack(secret, tc.opts)
|
|
if err != nil {
|
|
t.Fatalf("Pack: %v", err)
|
|
}
|
|
got, err := Unpack(env, tc.opts.Passphrase)
|
|
if err != nil {
|
|
t.Fatalf("Unpack: %v", err)
|
|
}
|
|
if !bytes.Equal(got, secret) {
|
|
t.Errorf("round-trip mismatch: got %q", got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWrongPassphraseFails(t *testing.T) {
|
|
env, err := Pack(secret, Options{Passphrase: []byte("right")})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := Unpack(env, []byte("wrong")); !errors.Is(err, ErrDecrypt) {
|
|
t.Errorf("got %v want ErrDecrypt", err)
|
|
}
|
|
}
|
|
|
|
func TestMissingPassphraseFails(t *testing.T) {
|
|
env, err := Pack(secret, Options{Passphrase: []byte("right")})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := Unpack(env, nil); !errors.Is(err, ErrPassphraseRequired) {
|
|
t.Errorf("got %v want ErrPassphraseRequired", err)
|
|
}
|
|
}
|
|
|
|
func TestTamperDetected(t *testing.T) {
|
|
env, err := Pack(secret, Options{Passphrase: []byte("right")})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
env[len(env)-6] ^= 0xFF // flip a byte inside the ciphertext body
|
|
if _, err := Unpack(env, []byte("right")); err == nil {
|
|
t.Error("expected an error on tampered envelope")
|
|
}
|
|
}
|
|
|
|
func TestChecksumMismatch(t *testing.T) {
|
|
env, err := Pack(secret, Options{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
env[len(env)-5] ^= 0xFF // flip a body byte on the plaintext path
|
|
if _, err := Unpack(env, nil); !errors.Is(err, ErrChecksumMismatch) {
|
|
t.Errorf("got %v want ErrChecksumMismatch", err)
|
|
}
|
|
}
|
|
|
|
func TestBadMagic(t *testing.T) {
|
|
env, _ := Pack(secret, Options{})
|
|
env[0] ^= 0xFF
|
|
if _, err := Unpack(env, nil); !errors.Is(err, ErrBadMagic) {
|
|
t.Errorf("got %v want ErrBadMagic", err)
|
|
}
|
|
}
|
|
|
|
func TestVersionReject(t *testing.T) {
|
|
env, _ := Pack(secret, Options{})
|
|
env[len(magic)] = 0x7F
|
|
if _, err := Unpack(env, nil); !errors.Is(err, ErrUnsupportedVersion) {
|
|
t.Errorf("got %v want ErrUnsupportedVersion", err)
|
|
}
|
|
}
|
|
|
|
func TestTruncated(t *testing.T) {
|
|
env, _ := Pack(secret, Options{})
|
|
if _, err := Unpack(env[:5], nil); !errors.Is(err, ErrTruncated) {
|
|
t.Errorf("got %v want ErrTruncated", err)
|
|
}
|
|
}
|
|
|
|
func TestEmptyPayload(t *testing.T) {
|
|
if _, err := Pack(nil, Options{}); !errors.Is(err, ErrEmptyPayload) {
|
|
t.Errorf("got %v want ErrEmptyPayload", err)
|
|
}
|
|
}
|
|
|
|
func TestHostileParamsRejected(t *testing.T) {
|
|
env, err := Pack(secret, Options{Passphrase: []byte("right")})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// zero out the Argon2 time field (offset magic+ver+flags+cipherID)
|
|
timeOff := len(magic) + 1 + 1 + 1
|
|
for i := 0; i < 4; i++ {
|
|
env[timeOff+i] = 0
|
|
}
|
|
if _, err := Unpack(env, []byte("right")); !errors.Is(err, ErrBadParams) {
|
|
t.Errorf("got %v want ErrBadParams", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateAndIsEncrypted(t *testing.T) {
|
|
plain, _ := Pack(secret, Options{})
|
|
enc, _ := Pack(secret, Options{Passphrase: []byte("k")})
|
|
|
|
if err := Validate(plain); err != nil {
|
|
t.Errorf("Validate(plain): %v", err)
|
|
}
|
|
if err := Validate([]byte("garbage")); err == nil {
|
|
t.Error("Validate(garbage) should fail")
|
|
}
|
|
if e, _ := IsEncrypted(plain); e {
|
|
t.Error("plain reported encrypted")
|
|
}
|
|
if e, _ := IsEncrypted(enc); !e {
|
|
t.Error("encrypted reported plain")
|
|
}
|
|
}
|
|
|
|
func TestStrengthParams(t *testing.T) {
|
|
if p := paramsForStrength(StrengthHigh); p.memory != argonHighMemory || p.time != argonHighTime {
|
|
t.Errorf("high params wrong: %+v", p)
|
|
}
|
|
if p := paramsForStrength(StrengthDefault); p.memory != argonDefaultMemory {
|
|
t.Errorf("default params wrong: %+v", p)
|
|
}
|
|
}
|
|
|
|
func TestOverheadMatchesEnvelope(t *testing.T) {
|
|
plain := []byte("a modest secret")
|
|
|
|
clear, err := Pack(plain, Options{})
|
|
if err != nil {
|
|
t.Fatalf("pack plaintext: %v", err)
|
|
}
|
|
if got := len(clear) - len(plain); got != Overhead(false) {
|
|
t.Errorf("plaintext overhead = %d, Overhead(false) = %d", got, Overhead(false))
|
|
}
|
|
|
|
sealed, err := Pack(plain, Options{Passphrase: []byte("pw"), Cipher: CipherChaCha20})
|
|
if err != nil {
|
|
t.Fatalf("pack encrypted: %v", err)
|
|
}
|
|
if got := len(sealed) - len(plain); got != Overhead(true) {
|
|
t.Errorf("encrypted overhead = %d, Overhead(true) = %d", got, Overhead(true))
|
|
}
|
|
}
|