From bb49c6a55d30ef11eb9e6cf2a84c52e602a55aa8 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 12 Mar 2026 00:04:37 +0100 Subject: [PATCH] Integrate new fake package and doppel into proxy --- mtglib/internal/tls/fake/client_side.go | 14 +++-- .../tls/fake/client_side_fuzz_test.go | 2 +- .../tls/fake/client_side_snapshot_test.go | 14 ++++- mtglib/internal/tls/fake/client_side_test.go | 54 +++++++++---------- mtglib/internal/tls/fake/server_side.go | 5 +- mtglib/internal/tls/fake/server_side_test.go | 8 +-- mtglib/proxy.go | 51 +++++++----------- network/v2/http.go | 13 +---- 8 files changed, 76 insertions(+), 85 deletions(-) diff --git a/mtglib/internal/tls/fake/client_side.go b/mtglib/internal/tls/fake/client_side.go index 8d89b5a..3b7e5a0 100644 --- a/mtglib/internal/tls/fake/client_side.go +++ b/mtglib/internal/tls/fake/client_side.go @@ -12,7 +12,6 @@ import ( "slices" "time" - "github.com/9seconds/mtg/v2/mtglib" "github.com/9seconds/mtg/v2/mtglib/internal/tls" ) @@ -37,7 +36,12 @@ type ClientHello struct { CipherSuite uint16 } -func ReadClientHello(conn net.Conn, secret mtglib.Secret, tolerateTimeSkewness time.Duration) (*ClientHello, error) { +func ReadClientHello( + conn net.Conn, + secret []byte, + hostname string, + tolerateTimeSkewness time.Duration, +) (*ClientHello, error) { if err := conn.SetReadDeadline(time.Now().Add(ClientHelloReadTimeout)); err != nil { return nil, fmt.Errorf("cannot set read deadline: %w", err) } @@ -75,11 +79,11 @@ func ReadClientHello(conn net.Conn, secret mtglib.Secret, tolerateTimeSkewness t return nil, fmt.Errorf("cannot parse SNI: %w", err) } - if !slices.Contains(sniHostnames, secret.Host) { - return nil, fmt.Errorf("cannot find %s in %v", secret.Host, sniHostnames) + if !slices.Contains(sniHostnames, hostname) { + return nil, fmt.Errorf("cannot find %s in %v", hostname, sniHostnames) } - digest := hmac.New(sha256.New, secret.Key[:]) + digest := hmac.New(sha256.New, secret) // we write a copy of the handshake with client random all nullified. digest.Write(handshakeCopyBuf.Next(RandomOffset)) handshakeCopyBuf.Next(RandomLen) diff --git a/mtglib/internal/tls/fake/client_side_fuzz_test.go b/mtglib/internal/tls/fake/client_side_fuzz_test.go index 109873b..28f3337 100644 --- a/mtglib/internal/tls/fake/client_side_fuzz_test.go +++ b/mtglib/internal/tls/fake/client_side_fuzz_test.go @@ -42,7 +42,7 @@ func FuzzReadClientHello(f *testing.F) { Twice(). Return(nil) - _, err := fake.ReadClientHello(r, secret, time.Hour) + _, err := fake.ReadClientHello(r, secret.Key[:], secret.Host, time.Hour) assert.Error(t, err) }) diff --git a/mtglib/internal/tls/fake/client_side_snapshot_test.go b/mtglib/internal/tls/fake/client_side_snapshot_test.go index fc60ce7..e4e0480 100644 --- a/mtglib/internal/tls/fake/client_side_snapshot_test.go +++ b/mtglib/internal/tls/fake/client_side_snapshot_test.go @@ -100,7 +100,12 @@ func (suite *ParseClientHelloSnapshotTestSuite) TestSnapshotOk() { connMock := suite.makeConn(snapshot.GetFull()) defer connMock.AssertExpectations(t) - hello, err := fake.ReadClientHello(connMock, suite.secret, TolerateTime) + hello, err := fake.ReadClientHello( + connMock, + suite.secret.Key[:], + suite.secret.Host, + TolerateTime, + ) require.NoError(t, err) assert.Equal(t, snapshot.GetRandom(), hello.Random[:]) @@ -131,7 +136,12 @@ func (suite *ParseClientHelloSnapshotTestSuite) TestSnapshotBad() { connMock := suite.makeConn(snapshot.GetFull()) defer connMock.AssertExpectations(t) - _, err = fake.ReadClientHello(connMock, suite.secret, TolerateTime) + _, err = fake.ReadClientHello( + connMock, + suite.secret.Key[:], + suite.secret.Host, + TolerateTime, + ) assert.ErrorIs(t, err, fake.ErrBadDigest) }) } diff --git a/mtglib/internal/tls/fake/client_side_test.go b/mtglib/internal/tls/fake/client_side_test.go index edf5901..2e66b6c 100644 --- a/mtglib/internal/tls/fake/client_side_test.go +++ b/mtglib/internal/tls/fake/client_side_test.go @@ -73,7 +73,7 @@ func (suite *ParseClientHello_TLSHeaderTestSuite) TestEmpty() { Once(). Return(errors.New("fail")) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorContains(err, "fail") } @@ -84,7 +84,7 @@ func (suite *ParseClientHello_TLSHeaderTestSuite) TestNothing() { Twice(). Return(nil) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorIs(err, io.EOF) } @@ -96,7 +96,7 @@ func (suite *ParseClientHello_TLSHeaderTestSuite) TestUnknownRecord() { }) suite.readBuf.WriteByte(10) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorContains(err, "unexpected record type 0xa") } @@ -107,7 +107,7 @@ func (suite *ParseClientHello_TLSHeaderTestSuite) TestUnknownProtocolVersion() { 0, 0, }) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorContains(err, "unexpected protocol version") } @@ -118,7 +118,7 @@ func (suite *ParseClientHello_TLSHeaderTestSuite) TestCannotReadRestOfRecord() { 0, 10, }) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorIs(err, io.EOF) } @@ -142,7 +142,7 @@ func (suite *ParseClientHelloHandshakeTestSuite) TestCannotReadHeader() { 10, }) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorContains(err, "cannot read handshake header") } @@ -152,7 +152,7 @@ func (suite *ParseClientHelloHandshakeTestSuite) TestIncorrectHandshakeType() { 10, 0, 0, 0, }) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorContains(err, "incorrect handshake type") } @@ -162,7 +162,7 @@ func (suite *ParseClientHelloHandshakeTestSuite) TestCannotReadHandshake() { 10, 0, 0, 0, }) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorIs(err, io.EOF) } @@ -192,14 +192,14 @@ func (suite *ParseClientHelloHandshakeBodyTestSuite) writeBody(body []byte) { func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotReadVersion() { suite.writeBody(nil) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorContains(err, "cannot read client version") } func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotReadRandom() { suite.writeBody([]byte{3, 3}) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorContains(err, "cannot read client random") } @@ -208,7 +208,7 @@ func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotReadSessionIDLeng suite.writeBody(body) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorContains(err, "cannot read session ID length") } @@ -218,7 +218,7 @@ func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotReadSessionID() { suite.writeBody(body) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorContains(err, "cannot read session id") } @@ -227,7 +227,7 @@ func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotReadCipherSuiteLe suite.writeBody(body) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorContains(err, "cannot read cipher suite length") } @@ -236,7 +236,7 @@ func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotReadFirstCipherSu suite.writeBody(body) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorContains(err, "cannot read first cipher suite") } @@ -246,7 +246,7 @@ func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotSkipRemainingCiph suite.writeBody(body) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorContains(err, "cannot skip remaining cipher suites") } @@ -256,7 +256,7 @@ func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotReadCompressionMe suite.writeBody(body) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorContains(err, "cannot read compression methods length") } @@ -267,7 +267,7 @@ func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotSkipCompressionMe suite.writeBody(body) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorContains(err, "cannot skip compression methods") } @@ -307,70 +307,70 @@ func (suite *ParseClientHelloSNITestSuite) writeExtensions(extensions []byte) { func (suite *ParseClientHelloSNITestSuite) TestCannotReadExtensionsLength() { suite.writeExtensions(nil) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorContains(err, "cannot read length of TLS extensions") } func (suite *ParseClientHelloSNITestSuite) TestCannotReadExtensions() { suite.writeExtensions([]byte{0, 10}) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorContains(err, "cannot read extensions") } func (suite *ParseClientHelloSNITestSuite) TestCannotReadExtensionType() { suite.writeExtensions([]byte{0, 1, 0xAB}) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorContains(err, "cannot read extension type") } func (suite *ParseClientHelloSNITestSuite) TestCannotReadExtensionLength() { suite.writeExtensions([]byte{0, 2, 0xFF, 0xFF}) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorContains(err, "length:") } func (suite *ParseClientHelloSNITestSuite) TestCannotReadExtensionData() { suite.writeExtensions([]byte{0, 4, 0xFF, 0xFF, 0, 5}) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorContains(err, "data: len") } func (suite *ParseClientHelloSNITestSuite) TestCannotReadSNIRecordLength() { suite.writeExtensions([]byte{0, 5, 0, 0, 0, 1, 0xAB}) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorContains(err, "cannot read the length of the SNI record") } func (suite *ParseClientHelloSNITestSuite) TestCannotReadSNIListType() { suite.writeExtensions([]byte{0, 6, 0, 0, 0, 2, 0, 1}) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorContains(err, "cannot read SNI list type") } func (suite *ParseClientHelloSNITestSuite) TestIncorrectSNIListType() { suite.writeExtensions([]byte{0, 7, 0, 0, 0, 3, 0, 1, 5}) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorContains(err, "incorrect SNI list type") } func (suite *ParseClientHelloSNITestSuite) TestCannotReadHostnameLength() { suite.writeExtensions([]byte{0, 8, 0, 0, 0, 4, 0, 2, 0, 0xAB}) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorContains(err, "incorrect length of the hostname") } func (suite *ParseClientHelloSNITestSuite) TestCannotReadHostname() { suite.writeExtensions([]byte{0, 9, 0, 0, 0, 5, 0, 3, 0, 0, 5}) - _, err := fake.ReadClientHello(suite.connMock, suite.secret, TolerateTime) + _, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime) suite.ErrorContains(err, "incorrect length of SNI hostname") } diff --git a/mtglib/internal/tls/fake/server_side.go b/mtglib/internal/tls/fake/server_side.go index 8f920c5..27ae0d6 100644 --- a/mtglib/internal/tls/fake/server_side.go +++ b/mtglib/internal/tls/fake/server_side.go @@ -8,7 +8,6 @@ import ( "encoding/binary" "io" - "github.com/9seconds/mtg/v2/mtglib" "github.com/9seconds/mtg/v2/mtglib/internal/tls" "golang.org/x/crypto/curve25519" ) @@ -35,7 +34,7 @@ var ( } ) -func SendServerHello(w io.Writer, secret mtglib.Secret, clientHello *ClientHello) ([]byte, error) { +func SendServerHello(w io.Writer, secret []byte, clientHello *ClientHello) ([]byte, error) { buf := &bytes.Buffer{} buf.Grow(tls.MaxRecordSize) @@ -46,7 +45,7 @@ func SendServerHello(w io.Writer, secret mtglib.Secret, clientHello *ClientHello generateNoise(noise) packet := buf.Bytes() - digest := hmac.New(sha256.New, secret.Key[:]) + digest := hmac.New(sha256.New, secret) digest.Write(clientHello.Random[:]) digest.Write(packet) diff --git a/mtglib/internal/tls/fake/server_side_test.go b/mtglib/internal/tls/fake/server_side_test.go index 05508ba..37d4246 100644 --- a/mtglib/internal/tls/fake/server_side_test.go +++ b/mtglib/internal/tls/fake/server_side_test.go @@ -38,7 +38,7 @@ func (suite *SendServerHelloTestSuite) SetupTest() { } func (suite *SendServerHelloTestSuite) TestRecordStructure() { - noise, err := fake.SendServerHello(suite.buf, suite.secret, suite.hello) + noise, err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello) suite.NoError(err) var rec bytes.Buffer @@ -65,7 +65,7 @@ func (suite *SendServerHelloTestSuite) TestRecordStructure() { } func (suite *SendServerHelloTestSuite) TestHMAC() { - noise, err := fake.SendServerHello(suite.buf, suite.secret, suite.hello) + noise, err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello) suite.NoError(err) packet := make([]byte, suite.buf.Len()) @@ -84,7 +84,7 @@ func (suite *SendServerHelloTestSuite) TestHMAC() { } func (suite *SendServerHelloTestSuite) TestHandshakePayload() { - _, err := fake.SendServerHello(suite.buf, suite.secret, suite.hello) + _, err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello) suite.NoError(err) packet := suite.buf.Bytes() @@ -106,7 +106,7 @@ func (suite *SendServerHelloTestSuite) TestHandshakePayload() { } func (suite *SendServerHelloTestSuite) TestChangeCipherSpec() { - _, err := fake.SendServerHello(suite.buf, suite.secret, suite.hello) + _, err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello) suite.NoError(err) // Skip first record diff --git a/mtglib/proxy.go b/mtglib/proxy.go index ce740a1..9445752 100644 --- a/mtglib/proxy.go +++ b/mtglib/proxy.go @@ -11,10 +11,11 @@ import ( "github.com/9seconds/mtg/v2/essentials" "github.com/9seconds/mtg/v2/mtglib/internal/dc" - "github.com/9seconds/mtg/v2/mtglib/internal/faketls" - "github.com/9seconds/mtg/v2/mtglib/internal/faketls/record" + "github.com/9seconds/mtg/v2/mtglib/internal/doppel" "github.com/9seconds/mtg/v2/mtglib/internal/obfuscation" "github.com/9seconds/mtg/v2/mtglib/internal/relay" + "github.com/9seconds/mtg/v2/mtglib/internal/tls" + "github.com/9seconds/mtg/v2/mtglib/internal/tls/fake" "github.com/panjf2000/ants/v2" ) @@ -32,6 +33,7 @@ type Proxy struct { workerPool *ants.PoolWithFunc telegram *dc.Telegram configUpdater *dc.PublicConfigUpdater + doppelGanger *doppel.Ganger clientObfuscatror obfuscation.Obfuscator secret Secret @@ -161,52 +163,39 @@ func (p *Proxy) Shutdown() { } func (p *Proxy) doFakeTLSHandshake(ctx *streamContext) bool { - rec := record.AcquireRecord() - defer record.ReleaseRecord(rec) - rewind := newConnRewind(ctx.clientConn) - if err := rec.Read(rewind); err != nil { + clientHello, err := fake.ReadClientHello( + rewind, + p.secret.Key[:], + p.secret.Host, + p.tolerateTimeSkewness, + ) + if err != nil { p.logger.InfoError("cannot read client hello", err) p.doDomainFronting(ctx, rewind) - return false } - hello, err := faketls.ParseClientHello(p.secret.Key[:], rec.Payload.Bytes()) - if err != nil { - p.logger.InfoError("cannot parse client hello", err) - p.doDomainFronting(ctx, rewind) - - return false - } - - if err := hello.Valid(p.secret.Host, p.tolerateTimeSkewness); err != nil { - p.logger. - BindStr("hostname", hello.Host). - BindStr("hello-time", hello.Time.String()). - InfoError("invalid faketls client hello", err) - p.doDomainFronting(ctx, rewind) - - return false - } - - if p.antiReplayCache.SeenBefore(hello.SessionID) { + if p.antiReplayCache.SeenBefore(clientHello.SessionID) { p.logger.Warning("replay attack has been detected!") p.eventStream.Send(p.ctx, NewEventReplayAttack(ctx.streamID)) p.doDomainFronting(ctx, rewind) - return false } - if err := faketls.SendWelcomePacket(rewind, p.secret.Key[:], hello); err != nil { + _, err = fake.SendServerHello(ctx.clientConn, p.secret.Key[:], clientHello) + if err != nil { p.logger.InfoError("cannot send welcome packet", err) - return false } - ctx.clientConn = &faketls.Conn{ - Conn: ctx.clientConn, + ctx.clientConn = tls.New(ctx.clientConn, true, true) + + ctx.clientConn, err = p.doppelGanger.NewConn(ctx.clientConn) + if err != nil { + p.logger.WarningError("cannot create connection", err) + return false } return true diff --git a/network/v2/http.go b/network/v2/http.go index 0504352..58e6a5e 100644 --- a/network/v2/http.go +++ b/network/v2/http.go @@ -1,9 +1,6 @@ package network -import ( - "crypto/tls" - "net/http" -) +import "net/http" type networkHTTPTransport struct { userAgent string @@ -15,11 +12,3 @@ func (n networkHTTPTransport) RoundTrip(req *http.Request) (*http.Response, erro return n.next.RoundTrip(req) //nolint: wrapcheck } - -func (n networkHTTPTransport) TrustTLS() { - tr := n.next.(*http.Transport) - if tr.TLSClientConfig == nil { - tr.TLSClientConfig = &tls.Config{} - } - tr.TLSClientConfig.InsecureSkipVerify = true -}