From 69203f3e23894232a033f74c84788bf96da437ff Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 22 Mar 2021 11:34:32 +0300 Subject: [PATCH] Add tests for obfuscated2 clientside --- .github/workflows/ci.yaml | 2 - cli/access.go | 5 +- cli/base.go | 4 +- config/config_test.go | 4 +- ipblocklist/firehol.go | 3 +- .../internal/obfuscated2/client_handshake.go | 2 +- .../obfuscated2/client_handshake_test.go | 85 +++++++++++++++++++ .../internal/obfuscated2/handshake_frame.go | 12 ++- mtglib/internal/obfuscated2/init_test.go | 83 ++++++++++++++++++ .../snapshot-ok-4529d55776e2d427 | 13 +++ .../snapshot-ok-585c944d672f60a2 | 13 +++ .../snapshot-ok-e3cedb3c8b05c39d | 13 +++ .../snapshot-ok-e7d2a1e5e479cf50 | 13 +++ .../snapshot-ok-eae4de34f1ab0bdf | 13 +++ .../snapshot-ok-ec9c53fa17f4b39b | 13 +++ .../snapshot-ok-ee4b5ad981dcce87 | 13 +++ .../snapshot-ok-f122ea426238564c | 13 +++ .../snapshot-ok-f3e117d7ac4e44f9 | 13 +++ .../snapshot-ok-f5e00132a81f7a61 | 13 +++ mtglib/proxy.go | 8 +- stats/prometheus_test.go | 4 +- 21 files changed, 320 insertions(+), 22 deletions(-) create mode 100644 mtglib/internal/obfuscated2/client_handshake_test.go create mode 100644 mtglib/internal/obfuscated2/init_test.go create mode 100644 mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-4529d55776e2d427 create mode 100644 mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-585c944d672f60a2 create mode 100644 mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-e3cedb3c8b05c39d create mode 100644 mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-e7d2a1e5e479cf50 create mode 100644 mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-eae4de34f1ab0bdf create mode 100644 mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-ec9c53fa17f4b39b create mode 100644 mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-ee4b5ad981dcce87 create mode 100644 mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-f122ea426238564c create mode 100644 mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-f3e117d7ac4e44f9 create mode 100644 mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-f5e00132a81f7a61 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4bd3b39..c122a34 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -30,8 +30,6 @@ jobs: strategy: matrix: go_version: - - ~1.14 - - ~1.15 - ^1.16 steps: - name: Checkout diff --git a/cli/access.go b/cli/access.go index b020372..d443f87 100644 --- a/cli/access.go +++ b/cli/access.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "net" "net/http" "net/url" @@ -123,11 +122,11 @@ func (c *Access) getIP(protocol string) net.IP { } defer func() { - io.Copy(ioutil.Discard, resp.Body) // nolint: errcheck + io.Copy(io.Discard, resp.Body) // nolint: errcheck resp.Body.Close() }() - data, err := ioutil.ReadAll(resp.Body) + data, err := io.ReadAll(resp.Body) if err != nil { return nil } diff --git a/cli/base.go b/cli/base.go index 9c19ec1..6b6f892 100644 --- a/cli/base.go +++ b/cli/base.go @@ -2,9 +2,9 @@ package cli import ( "fmt" - "io/ioutil" "net" "net/url" + "os" "github.com/9seconds/mtg/v2/config" "github.com/9seconds/mtg/v2/mtglib" @@ -19,7 +19,7 @@ type base struct { } func (b *base) ReadConfig(version string) error { - content, err := ioutil.ReadFile(b.ConfigPath) + content, err := os.ReadFile(b.ConfigPath) if err != nil { return fmt.Errorf("cannot read config file: %w", err) } diff --git a/config/config_test.go b/config/config_test.go index 3233c13..8ef99df 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -1,7 +1,7 @@ package config_test import ( - "io/ioutil" + "os" "path/filepath" "testing" @@ -14,7 +14,7 @@ type ConfigTestSuite struct { } func (suite *ConfigTestSuite) ReadConfig(filename string) []byte { - data, err := ioutil.ReadFile(filepath.Join("testdata", filename)) + data, err := os.ReadFile(filepath.Join("testdata", filename)) suite.NoError(err) return data diff --git a/ipblocklist/firehol.go b/ipblocklist/firehol.go index 49f1be7..ee1d473 100644 --- a/ipblocklist/firehol.go +++ b/ipblocklist/firehol.go @@ -5,7 +5,6 @@ import ( "context" "fmt" "io" - "io/ioutil" "net" "net/http" "net/url" @@ -216,7 +215,7 @@ func (f *Firehol) updateRemoteURL(ctx context.Context, url string, }(ctx, resp.Body) defer func(rc io.ReadCloser) { - io.Copy(ioutil.Discard, rc) // nolint: errcheck + io.Copy(io.Discard, rc) // nolint: errcheck rc.Close() }(resp.Body) diff --git a/mtglib/internal/obfuscated2/client_handshake.go b/mtglib/internal/obfuscated2/client_handshake.go index 08bef5d..93a164e 100644 --- a/mtglib/internal/obfuscated2/client_handshake.go +++ b/mtglib/internal/obfuscated2/client_handshake.go @@ -8,7 +8,7 @@ import ( "io" ) -func ClientHandshake(secret []byte, reader io.Reader) (int16, cipher.Stream, cipher.Stream, error) { +func ClientHandshake(secret []byte, reader io.Reader) (int, cipher.Stream, cipher.Stream, error) { handshake := clientHandhakeFrame{} if _, err := io.ReadFull(reader, handshake.data[:]); err != nil { diff --git a/mtglib/internal/obfuscated2/client_handshake_test.go b/mtglib/internal/obfuscated2/client_handshake_test.go new file mode 100644 index 0000000..d112f44 --- /dev/null +++ b/mtglib/internal/obfuscated2/client_handshake_test.go @@ -0,0 +1,85 @@ +package obfuscated2_test + +import ( + "bytes" + "testing" + + "github.com/9seconds/mtg/v2/mtglib/internal/obfuscated2" + "github.com/9seconds/mtg/v2/testlib" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" +) + +type ClientHandshakeTestSuite struct { + suite.Suite + SnapshotTestSuite +} + +func (suite *ClientHandshakeTestSuite) SetupSuite() { + suite.NoError(suite.IngestSnapshots("client_snapshots", "snapshot-ok-")) +} + +func (suite *ClientHandshakeTestSuite) TestCannotRead() { + buf := bytes.NewBuffer([]byte{1, 2, 3}) + _, _, _, err := obfuscated2.ClientHandshake([]byte{1, 2, 3}, buf) // nolint: dogsled + + suite.Error(err) +} + +func (suite *ClientHandshakeTestSuite) TestOk() { + for nameV, snapshotV := range suite.snapshots { + snapshot := snapshotV + + suite.T().Run(nameV, func(t *testing.T) { + buf := bytes.NewBuffer(snapshot.Frame.data) + + dc, encryptor, decryptor, err := obfuscated2.ClientHandshake( + snapshot.Secret.data, buf) + assert.NoError(t, err) + assert.EqualValues(t, snapshot.DC, dc) + + writeData := make([]byte, len(snapshot.Encrypted.Text.data)) + readData := make([]byte, len(snapshot.Decrypted.Text.data)) + + connMock := &testlib.NetConnMock{} + connMock.On("Read", mock.Anything). + Once(). + Return(len(snapshot.Decrypted.Text.data), nil). + Run(func(args mock.Arguments) { + arr := args.Get(0).([]byte) + copy(arr, snapshot.Decrypted.Cipher.data) + }) + connMock.On("Write", mock.Anything). + Once(). + Return(len(snapshot.Encrypted.Text.data), nil). + Run(func(args mock.Arguments) { + arr := args.Get(0).([]byte) + copy(writeData, arr) + }) + + conn := &obfuscated2.Conn{ + Conn: connMock, + Encryptor: encryptor, + Decryptor: decryptor, + } + + n, err := conn.Read(readData) + assert.Equal(t, len(readData), n) + assert.NoError(t, err) + assert.Equal(t, snapshot.Decrypted.Text.data, readData) + + n, err = conn.Write(snapshot.Encrypted.Text.data) + assert.Equal(t, len(writeData), n) + assert.NoError(t, err) + assert.Equal(t, snapshot.Encrypted.Cipher.data, writeData) + + connMock.AssertExpectations(t) + }) + } +} + +func TestClientHandshake(t *testing.T) { + t.Parallel() + suite.Run(t, &ClientHandshakeTestSuite{}) +} diff --git a/mtglib/internal/obfuscated2/handshake_frame.go b/mtglib/internal/obfuscated2/handshake_frame.go index 919d7bb..1309981 100644 --- a/mtglib/internal/obfuscated2/handshake_frame.go +++ b/mtglib/internal/obfuscated2/handshake_frame.go @@ -35,10 +35,18 @@ type handshakeFrame struct { data [handshakeFrameLen]byte } -func (h *handshakeFrame) dc() int16 { +func (h *handshakeFrame) dc() int { data := h.data[handshakeFrameOffsetDC:handshakeFrameOffsetEnd] + idx := int16(binary.LittleEndian.Uint16(data)) - return int16(binary.LittleEndian.Uint16(data)) + switch { + case idx > 0: + return int(idx) - 1 + case idx < 0: + return -int(idx + 1) + default: + return 0 + } } func (h *handshakeFrame) key() []byte { diff --git a/mtglib/internal/obfuscated2/init_test.go b/mtglib/internal/obfuscated2/init_test.go new file mode 100644 index 0000000..001c84b --- /dev/null +++ b/mtglib/internal/obfuscated2/init_test.go @@ -0,0 +1,83 @@ +package obfuscated2_test + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" +) + +type snapshotBytes struct { + data []byte +} + +func (s snapshotBytes) MarshalText() ([]byte, error) { + if len(s.data) == 0 { + return nil, nil + } + + return []byte(base64.RawStdEncoding.EncodeToString(s.data)), nil +} + +func (s *snapshotBytes) UnmarshalText(data []byte) error { + val, err := base64.RawStdEncoding.DecodeString(string(data)) + if err != nil { + return fmt.Errorf("cannot unmarshal %v: %w", len(val), err) + } + + s.data = val + + return nil +} + +type Obfuscated2Snapshot struct { + Secret snapshotBytes `json:"secret"` + Frame snapshotBytes `json:"frame"` + DC int16 `json:"dc"` + Encrypted struct { + Text snapshotBytes `json:"text"` + Cipher snapshotBytes `json:"cipher"` + } `json:"encrypted"` + Decrypted struct { + Text snapshotBytes `json:"text"` + Cipher snapshotBytes `json:"cipher"` + } `json:"decrypted"` +} + +type SnapshotTestSuite struct { + snapshots map[string]*Obfuscated2Snapshot +} + +func (suite *SnapshotTestSuite) IngestSnapshots(dirname, namePrefix string) error { + suite.snapshots = map[string]*Obfuscated2Snapshot{} + + files, err := os.ReadDir(filepath.Join("testdata", dirname)) + if err != nil { + return fmt.Errorf("cannot ingest snapshots: %w", err) + } + + for _, v := range files { + if !strings.HasPrefix(v.Name(), namePrefix) { + continue + } + + filename := filepath.Join("testdata", dirname, v.Name()) + + contents, err := os.ReadFile(filename) + if err != nil { + return fmt.Errorf("cannot read %s: %w", filename, err) + } + + value := &Obfuscated2Snapshot{} + + if err := json.Unmarshal(contents, value); err != nil { + return fmt.Errorf("cannot unmarshal %s: %w", filename, err) + } + + suite.snapshots[v.Name()] = value + } + + return nil +} diff --git a/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-4529d55776e2d427 b/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-4529d55776e2d427 new file mode 100644 index 0000000..00701aa --- /dev/null +++ b/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-4529d55776e2d427 @@ -0,0 +1,13 @@ +{ + "secret": "NnoYmu4Y+jHBkAVO/UqOlQ", + "frame": "gDcXwaMY4RwlR+nJw+ILDr123UJHHjjE/U5pF4m/Y04AmH7lEpEL6UYRnIYDbDlOHSDxc1ToziPvNlJJh8RMow", + "dc": 1, + "encrypted": { + "text": "AQIDBAUGBwgJCg", + "cipher": "wZV3TR39l9nRoQ" + }, + "decrypted": { + "text": "4wZj6mUUew", + "cipher": "YWJjZGVmZw" + } +} diff --git a/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-585c944d672f60a2 b/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-585c944d672f60a2 new file mode 100644 index 0000000..c098a6e --- /dev/null +++ b/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-585c944d672f60a2 @@ -0,0 +1,13 @@ +{ + "secret": "NnoYmu4Y+jHBkAVO/UqOlQ", + "frame": "M2WyxeiwIQB+ZOFxNzSNHtu9OdESkfxv3JkKFimCxUoYA3BD/Ql9nXB/OIonCKLUKCcS0VzZ2P6/+5oQ9GI8YA", + "dc": 1, + "encrypted": { + "text": "AQIDBAUGBwgJCg", + "cipher": "tzAwrCz00odERg" + }, + "decrypted": { + "text": "QkIvwGQDgA", + "cipher": "YWJjZGVmZw" + } +} diff --git a/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-e3cedb3c8b05c39d b/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-e3cedb3c8b05c39d new file mode 100644 index 0000000..7e58092 --- /dev/null +++ b/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-e3cedb3c8b05c39d @@ -0,0 +1,13 @@ +{ + "secret": "NnoYmu4Y+jHBkAVO/UqOlQ", + "frame": "ZEZY1K3SZZgHX2MgMeYMMVoIPYR6eP+bgKxjI7IHl6sPLhfH2jRitS7/VA6Kz8E2L+uLqVom7x4zO+D5Q5iARA", + "dc": 1, + "encrypted": { + "text": "AQIDBAUGBwgJCg", + "cipher": "WptF2u4ZSDMZxQ" + }, + "decrypted": { + "text": "DT+Ob9yGWA", + "cipher": "YWJjZGVmZw" + } +} diff --git a/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-e7d2a1e5e479cf50 b/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-e7d2a1e5e479cf50 new file mode 100644 index 0000000..7e7cfa9 --- /dev/null +++ b/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-e7d2a1e5e479cf50 @@ -0,0 +1,13 @@ +{ + "secret": "NnoYmu4Y+jHBkAVO/UqOlQ", + "frame": "/rbO1fCVLR7o28nrc9inrdDU+4Z4uOqbC2kMnNnzItv0fJmn4hcUXK6YBJQZVI01i7rFlgiCtTgrHtyfQp9p9w", + "dc": 1, + "encrypted": { + "text": "AQIDBAUGBwgJCg", + "cipher": "jKZzALBemf72Cw" + }, + "decrypted": { + "text": "fMOlRiN20A", + "cipher": "YWJjZGVmZw" + } +} diff --git a/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-eae4de34f1ab0bdf b/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-eae4de34f1ab0bdf new file mode 100644 index 0000000..145cf0f --- /dev/null +++ b/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-eae4de34f1ab0bdf @@ -0,0 +1,13 @@ +{ + "secret": "NnoYmu4Y+jHBkAVO/UqOlQ", + "frame": "SfqaPWt1F8WTfrTkTl06s7F1nUcBR6AUp5uniCcYpVCJvgYpOO3TQHUZbLaFbI5qYRjJBQDe9OpXDJjR/5z4lg", + "dc": 1, + "encrypted": { + "text": "AQIDBAUGBwgJCg", + "cipher": "0DtHeQPN2bv7Qw" + }, + "decrypted": { + "text": "BWPiYKe4bA", + "cipher": "YWJjZGVmZw" + } +} diff --git a/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-ec9c53fa17f4b39b b/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-ec9c53fa17f4b39b new file mode 100644 index 0000000..01b7ee0 --- /dev/null +++ b/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-ec9c53fa17f4b39b @@ -0,0 +1,13 @@ +{ + "secret": "NnoYmu4Y+jHBkAVO/UqOlQ", + "frame": "MF6BfhvxyI61vFHZN/ecrDF1sZux/JdgVjKX1Yzy7SmBhYu+8bS25ta8iFsj/4y2moBpeNFp7rqekCE3FRb29A", + "dc": 1, + "encrypted": { + "text": "AQIDBAUGBwgJCg", + "cipher": "MujITanGx9xf9g" + }, + "decrypted": { + "text": "0xVTx0JKug", + "cipher": "YWJjZGVmZw" + } +} diff --git a/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-ee4b5ad981dcce87 b/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-ee4b5ad981dcce87 new file mode 100644 index 0000000..7adb26f --- /dev/null +++ b/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-ee4b5ad981dcce87 @@ -0,0 +1,13 @@ +{ + "secret": "NnoYmu4Y+jHBkAVO/UqOlQ", + "frame": "YY3y0amcDqggxeNbnyyacKl++b3Q7X0XL9coxctXxJOYZQ/uXIaWFm1KD3s0VIKQ6C7NqJ8hnnJOcpp2Bdau6A", + "dc": 1, + "encrypted": { + "text": "AQIDBAUGBwgJCg", + "cipher": "mni7eHb24tufLQ" + }, + "decrypted": { + "text": "Aqi/2cgHXA", + "cipher": "YWJjZGVmZw" + } +} diff --git a/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-f122ea426238564c b/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-f122ea426238564c new file mode 100644 index 0000000..421d633 --- /dev/null +++ b/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-f122ea426238564c @@ -0,0 +1,13 @@ +{ + "secret": "NnoYmu4Y+jHBkAVO/UqOlQ", + "frame": "k0f9Sv7V7svvxmHlIP4Ajg0sTGb0NXHfJDk1h6VgAnV0my1F+NU8KPG35kqXC9IDQZK/6fOABb7npO8/TCHEzQ", + "dc": 1, + "encrypted": { + "text": "AQIDBAUGBwgJCg", + "cipher": "efUzYxRvqEYPlw" + }, + "decrypted": { + "text": "FFPwzZkL2A", + "cipher": "YWJjZGVmZw" + } +} diff --git a/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-f3e117d7ac4e44f9 b/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-f3e117d7ac4e44f9 new file mode 100644 index 0000000..e89bc3c --- /dev/null +++ b/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-f3e117d7ac4e44f9 @@ -0,0 +1,13 @@ +{ + "secret": "NnoYmu4Y+jHBkAVO/UqOlQ", + "frame": "NiK5AnDAJw+fsytYBnxOmcDl6jx3uQECznBS4WIaHXGWP0tcioPikE1mVtkp33aXT7bCfFlst+b0PvcldGSARw", + "dc": 1, + "encrypted": { + "text": "AQIDBAUGBwgJCg", + "cipher": "56aF+oauHVDbPQ" + }, + "decrypted": { + "text": "KycK+00LvQ", + "cipher": "YWJjZGVmZw" + } +} diff --git a/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-f5e00132a81f7a61 b/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-f5e00132a81f7a61 new file mode 100644 index 0000000..6f84d72 --- /dev/null +++ b/mtglib/internal/obfuscated2/testdata/client_snapshots/snapshot-ok-f5e00132a81f7a61 @@ -0,0 +1,13 @@ +{ + "secret": "NnoYmu4Y+jHBkAVO/UqOlQ", + "frame": "nYvAavvf0TywZ4hkfGEgf6ZmnIUsDWjRKzLktqF65D+3ha/LqViPdpvsexguXBd5HClDaY6YNXjb1TbjWWfYGQ", + "dc": 1, + "encrypted": { + "text": "AQIDBAUGBwgJCg", + "cipher": "t4GyXOdaa/phBw" + }, + "decrypted": { + "text": "lWNj5kAaug", + "cipher": "YWJjZGVmZw" + } +} diff --git a/mtglib/proxy.go b/mtglib/proxy.go index 1dd76cd..312a1bf 100644 --- a/mtglib/proxy.go +++ b/mtglib/proxy.go @@ -100,12 +100,8 @@ func (p *Proxy) doObfuscated2Handshake(ctx *streamContext) error { return fmt.Errorf("cannot process client handshake: %w", err) } - if dc < 0 { - dc = -dc - } - - ctx.dc = int(dc) - ctx.logger = ctx.logger.BindInt("dc", ctx.dc) + ctx.dc = dc + ctx.logger = ctx.logger.BindInt("dc", dc) ctx.clientConn = &obfuscated2.Conn{ Conn: ctx.clientConn, Encryptor: encryptor, diff --git a/stats/prometheus_test.go b/stats/prometheus_test.go index 4b2175a..00623cd 100644 --- a/stats/prometheus_test.go +++ b/stats/prometheus_test.go @@ -2,7 +2,7 @@ package stats_test import ( "fmt" - "io/ioutil" + "io" "net" "net/http" "testing" @@ -32,7 +32,7 @@ func (suite *PrometheusTestSuite) Get() (string, error) { defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) + data, err := io.ReadAll(resp.Body) if err != nil { return "", err // nolint: wrapcheck }