Integrate new fake package and doppel into proxy

This commit is contained in:
9seconds
2026-03-12 19:07:11 +01:00
parent 37f8d18be5
commit bb49c6a55d
8 changed files with 76 additions and 85 deletions
+9 -5
View File
@@ -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)
@@ -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)
})
@@ -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)
})
}
+27 -27
View File
@@ -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")
}
+2 -3
View File
@@ -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)
+4 -4
View File
@@ -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
+20 -31
View File
@@ -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