/* SPDX-License-Identifier: MIT * * Phobos */ package cobf import ( "bytes" "math/rand" "testing" ) var testKeys = [][]byte{ []byte("k"), []byte("phobos"), []byte("Ic0OGtSf1BdMmMDzs7GmYRuPS/HGmNXsSU9EOWEeuQI="), bytes.Repeat([]byte{0xFE}, 200), } var wireLengths = []int{32, 64, 92, 148, 176, 1024, 1420, 2000, 9000} func wirePacket(rng *rand.Rand, length int) []byte { payload := make([]byte, length) rng.Read(payload) payload[0] = byte(1 + rng.Intn(4)) payload[1], payload[2], payload[3] = 0, 0, 0 return payload } func roundTrip(t *testing.T, key, payload []byte, obfuscateBytes int) { t.Helper() buf := make([]byte, 65535) copy(buf, payload) n := Encode(buf, len(payload), key, 4, obfuscateBytes) if n < 4 { t.Fatalf("encode returned %d: keylen=%d length=%d obf=%d", n, len(key), len(payload), obfuscateBytes) } got, _ := Decode(buf, n, key, obfuscateBytes) if got != len(payload) || !bytes.Equal(buf[:got], payload) { t.Fatalf("round trip corrupted the payload: keylen=%d length=%d obf=%d encoded=%d decoded=%d", len(key), len(payload), obfuscateBytes, n, got) } } // Covers the window where the encoder used to pick a different obfuscation // span than the decoder: obfuscate-bytes at or above the payload length. func TestEncodeDecodeRoundTrip(t *testing.T) { rng := rand.New(rand.NewSource(1)) for _, key := range testKeys { for _, obfuscateBytes := range []int{0, 16, 64, 148, 200, 512} { for _, length := range wireLengths { for range 16 { roundTrip(t, key, wirePacket(rng, length), obfuscateBytes) } } } } } func TestStreamCipherRoundTrip(t *testing.T) { rng := rand.New(rand.NewSource(2)) for _, key := range testKeys { for _, length := range []int{1, 64, 65, 1000, 20000} { payload := make([]byte, length) rng.Read(payload) buf := bytes.Clone(payload) split := length / 3 enc := NewStreamCipher(key) enc.Apply(buf[:split]) enc.Apply(buf[split:]) if length > 4 && bytes.Equal(buf, payload) { t.Fatalf("stream cipher left the payload untouched at length %d", length) } dec := NewStreamCipher(key) dec.Apply(buf[:split]) dec.Apply(buf[split:]) if !bytes.Equal(buf, payload) { t.Fatalf("stream cipher round trip failed: keylen=%d length=%d", len(key), length) } } } } func keystream(key []byte, length int) []byte { buf := make([]byte, length) XOR(buf, length, key) return buf } // The C keystream cache is thread-local and was once keyed by packet length // and key length alone, so a second key of the same length silently inherited // the first key's keystream -- and the poison outlived the goroutine, because // Go reuses OS threads. Two keys of equal length must produce different // keystreams for the same packet length. func TestKeysOfEqualLengthKeepSeparateKeystreams(t *testing.T) { keyA := []byte("aaaaaa") keyB := []byte("bbbbbb") if len(keyA) != len(keyB) { t.Fatal("the test needs two keys of equal length") } for _, length := range []int{32, 148, 1420, 1500} { a := keystream(keyA, length) b := keystream(keyB, length) if bytes.Equal(a, b) { t.Fatalf("keys of equal length share a keystream at length %d: %x", length, a[:8]) } } rng := rand.New(rand.NewSource(3)) for range 200 { for _, length := range []int{32, 148, 1420} { roundTrip(t, keyA, wirePacket(rng, length), 0) roundTrip(t, keyB, wirePacket(rng, length), 0) } } }