FILE / ScuroNeko/mtg

mtglib/internal/tls/fake/server_side_test.go

Исходный файл и его история в репозитории.
FILE 80213ad35dd62297bef399e6b762052742a56aeb
Files
mtg/mtglib/internal/tls/fake/server_side_test.go
T
Alexey Dolotov 80213ad35d Add dynamic cert noise calibration for FakeTLS handshake
The hardcoded noise range (2500-4700 bytes) in the FakeTLS ServerHello
does not match the real certificate chain sizes of many popular fronting
domains (e.g., dl.google.com ≈ 6480 bytes, microsoft.com ≈ 13004 bytes).
This makes the proxy detectable by DPI systems that compare the
ApplicationData size with the real cert chain size for the SNI domain.

On startup, probe the fronting domain's actual TLS handshake size and
use the measured value ± jitter instead of the static range. Falls back
to the legacy 2500-4700 range if the probe fails.

Also adds optional caching of probe results between restarts
(noise-cache-path, noise-cache-ttl) and a configurable probe count
(noise-probe-count) under [defense.doppelganger].

Closes #408
2026-03-26 23:38:58 +03:00

157 lines
4.1 KiB
Go

package fake_test
import (
"bytes"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"testing"
"github.com/9seconds/mtg/v2/mtglib"
"github.com/9seconds/mtg/v2/mtglib/internal/tls"
"github.com/9seconds/mtg/v2/mtglib/internal/tls/fake"
"github.com/stretchr/testify/suite"
)
type SendServerHelloTestSuite struct {
suite.Suite
hello *fake.ClientHello
buf *bytes.Buffer
secret mtglib.Secret
}
func (suite *SendServerHelloTestSuite) SetupTest() {
suite.hello = &fake.ClientHello{
CipherSuite: 4867,
SessionID: make([]byte, 32),
}
_, err := rand.Read(suite.hello.SessionID)
suite.NoError(err)
_, err = rand.Read(suite.hello.Random[:])
suite.NoError(err)
suite.buf = &bytes.Buffer{}
suite.secret = mtglib.GenerateSecret("google.com")
}
func (suite *SendServerHelloTestSuite) TestRecordStructure() {
err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello, fake.NoiseParams{})
suite.NoError(err)
var rec bytes.Buffer
recordType, _, err := tls.ReadRecord(suite.buf, &rec)
suite.NoError(err)
suite.Equal(byte(tls.TypeHandshake), recordType)
rec.Reset()
recordType, _, err = tls.ReadRecord(suite.buf, &rec)
suite.NoError(err)
suite.Equal(byte(tls.TypeChangeCipherSpec), recordType)
rec.Reset()
recordType, length, err := tls.ReadRecord(suite.buf, &rec)
suite.NoError(err)
suite.Equal(byte(tls.TypeApplicationData), recordType)
suite.Greater(length, int64(2500))
suite.Empty(suite.buf.Bytes())
}
func (suite *SendServerHelloTestSuite) TestHMAC() {
err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello, fake.NoiseParams{})
suite.NoError(err)
packet := make([]byte, suite.buf.Len())
copy(packet, suite.buf.Bytes())
random := make([]byte, fake.RandomLen)
copy(random, packet[fake.RandomOffset:])
copy(packet[fake.RandomOffset:], make([]byte, fake.RandomLen))
mac := hmac.New(sha256.New, suite.secret.Key[:])
mac.Write(suite.hello.Random[:])
mac.Write(packet)
suite.Equal(random, mac.Sum(nil))
}
func (suite *SendServerHelloTestSuite) TestHandshakePayload() {
err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello, fake.NoiseParams{})
suite.NoError(err)
packet := suite.buf.Bytes()
// TLS record header: type(1) + version(2) + length(2)
suite.Equal(byte(tls.TypeHandshake), packet[0])
suite.Equal([]byte{3, 3}, packet[1:3])
// Handshake header: type(1) + uint24_length(3)
suite.Equal(byte(fake.TypeHandshakeServer), packet[5])
// ServerHello version
suite.Equal([]byte{3, 3}, packet[9:11])
// Session ID
sessionIDOffset := fake.RandomOffset + fake.RandomLen
suite.Equal(byte(len(suite.hello.SessionID)), packet[sessionIDOffset])
suite.Equal(suite.hello.SessionID, packet[sessionIDOffset+1:sessionIDOffset+1+len(suite.hello.SessionID)])
}
func (suite *SendServerHelloTestSuite) TestChangeCipherSpec() {
err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello, fake.NoiseParams{})
suite.NoError(err)
// Skip first record
var rec bytes.Buffer
_, _, err = tls.ReadRecord(suite.buf, &rec)
suite.NoError(err)
// Read ChangeCipherSpec record
rec.Reset()
recordType, length, err := tls.ReadRecord(suite.buf, &rec)
suite.NoError(err)
suite.Equal(byte(tls.TypeChangeCipherSpec), recordType)
suite.Equal(int64(1), length)
suite.Equal([]byte{fake.ChangeCipherValue}, rec.Bytes())
}
func (suite *SendServerHelloTestSuite) TestCalibratedNoiseSize() {
noise := fake.NoiseParams{Mean: 6480, Jitter: 100}
err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello, noise)
suite.NoError(err)
var rec bytes.Buffer
// Skip ServerHello
_, _, err = tls.ReadRecord(suite.buf, &rec)
suite.NoError(err)
// Skip ChangeCipherSpec
rec.Reset()
_, _, err = tls.ReadRecord(suite.buf, &rec)
suite.NoError(err)
// Read noise ApplicationData
rec.Reset()
recordType, length, err := tls.ReadRecord(suite.buf, &rec)
suite.NoError(err)
suite.Equal(byte(tls.TypeApplicationData), recordType)
// Should be within mean ± jitter range.
suite.GreaterOrEqual(length, int64(noise.Mean-noise.Jitter))
suite.LessOrEqual(length, int64(noise.Mean+noise.Jitter))
}
func TestSendServerHello(t *testing.T) {
t.Parallel()
suite.Run(t, &SendServerHelloTestSuite{})
}