Procházet zdrojové kódy

Merge pull request #409 from dolonet/cert-noise-calibration

Add dynamic cert noise calibration for FakeTLS handshake
tags/v2.2.5^2^2
Sergei Arkhipov před 1 měsícem
rodič
revize
cc4b6ce2f4
Žádný účet není propojen s e-mailovou adresou tvůrce revize

+ 4
- 4
internal/config/config.go Zobrazit soubor

@@ -50,10 +50,10 @@ type Config struct {
50 50
 		Blocklist    ListConfig `json:"blocklist"`
51 51
 		Allowlist    ListConfig `json:"allowlist"`
52 52
 		Doppelganger struct {
53
-			URLs       []TypeHttpsURL  `json:"urls"`
54
-			Repeats    TypeConcurrency `json:"repeats_per_raid"`
55
-			UpdateEach TypeDuration    `json:"raid_each"`
56
-			DRS        TypeBool        `json:"drs"`
53
+			URLs            []TypeHttpsURL  `json:"urls"`
54
+			Repeats         TypeConcurrency `json:"repeats_per_raid"`
55
+			UpdateEach      TypeDuration    `json:"raid_each"`
56
+			DRS             TypeBool        `json:"drs"`
57 57
 		} `json:"doppelganger"`
58 58
 	} `json:"defense"`
59 59
 	Network struct {

+ 4
- 4
internal/config/parse.go Zobrazit soubor

@@ -45,10 +45,10 @@ type tomlConfig struct {
45 45
 			UpdateEach          string   `toml:"update-each" json:"updateEach,omitempty"`
46 46
 		} `toml:"allowlist" json:"allowlist,omitempty"`
47 47
 		Doppelganger struct {
48
-			URLs       []string `toml:"urls" json:"urls,omitempty"`
49
-			Repeats    uint     `toml:"repeats-per-raid" json:"repeats_per_raid,omitempty"`
50
-			UpdateEach string   `toml:"raid-each" json:"raid_each,omitempty"`
51
-			DRS        bool     `toml:"drs" json:"drs,omitempty"`
48
+			URLs            []string `toml:"urls" json:"urls,omitempty"`
49
+			Repeats         uint     `toml:"repeats-per-raid" json:"repeats_per_raid,omitempty"`
50
+			UpdateEach      string   `toml:"raid-each" json:"raid_each,omitempty"`
51
+			DRS             bool     `toml:"drs" json:"drs,omitempty"`
52 52
 		} `toml:"doppelganger" json:"doppelganger,omitempty"`
53 53
 	} `toml:"defense" json:"defense,omitempty"`
54 54
 	Network struct {

+ 89
- 7
mtglib/internal/doppel/ganger.go Zobrazit soubor

@@ -2,7 +2,9 @@ package doppel
2 2
 
3 3
 import (
4 4
 	"context"
5
+	"fmt"
5 6
 	"sync"
7
+	"sync/atomic"
6 8
 	"time"
7 9
 
8 10
 	"github.com/9seconds/mtg/v2/essentials"
@@ -12,8 +14,22 @@ const (
12 14
 	DoppelGangerMaxDurations  = 4096
13 15
 	DoppelGangerScoutRaidEach = 6 * time.Hour
14 16
 	DoppelGangerScoutRepeats  = 10
17
+
18
+	MinCertSizesToCalculate = 3
15 19
 )
16 20
 
21
+// NoiseParams holds the measured cert chain size for FakeTLS noise calibration.
22
+// If Mean is 0, the caller should use a legacy fallback.
23
+type NoiseParams struct {
24
+	Mean   int
25
+	Jitter int
26
+}
27
+
28
+type scoutRaidResult struct {
29
+	durations []time.Duration
30
+	certSizes []int
31
+}
32
+
17 33
 type gangerConnRequest struct {
18 34
 	ret     chan<- Conn
19 35
 	payload essentials.Conn
@@ -33,6 +49,9 @@ type Ganger struct {
33 49
 
34 50
 	stats     *Stats
35 51
 	durations []time.Duration
52
+	certSizes []int
53
+
54
+	noiseParams atomic.Pointer[NoiseParams]
36 55
 
37 56
 	connRequests chan gangerConnRequest
38 57
 }
@@ -48,6 +67,16 @@ func (g *Ganger) Run() {
48 67
 	})
49 68
 }
50 69
 
70
+// NoiseParams returns the current cert-size-based noise parameters.
71
+// Returns zero-value NoiseParams if not yet measured (caller should use fallback).
72
+func (g *Ganger) NoiseParams() NoiseParams {
73
+	if p := g.noiseParams.Load(); p != nil {
74
+		return *p
75
+	}
76
+
77
+	return NoiseParams{}
78
+}
79
+
51 80
 func (g *Ganger) NewConn(conn essentials.Conn) (Conn, error) {
52 81
 	rvChan := make(chan Conn)
53 82
 	req := gangerConnRequest{
@@ -81,7 +110,7 @@ func (g *Ganger) run() {
81 110
 		}
82 111
 	}()
83 112
 
84
-	scoutCollectedChan := make(chan []time.Duration)
113
+	scoutCollectedChan := make(chan scoutRaidResult)
85 114
 	currentScoutCollectedChan := scoutCollectedChan
86 115
 
87 116
 	updatedStatsChan := make(chan *Stats)
@@ -94,18 +123,29 @@ func (g *Ganger) run() {
94 123
 		select {
95 124
 		case <-g.ctx.Done():
96 125
 			return
97
-		case durations := <-currentScoutCollectedChan:
98
-			g.durations = append(g.durations, durations...)
126
+		case result := <-currentScoutCollectedChan:
127
+			g.durations = append(g.durations, result.durations...)
99 128
 
100 129
 			if len(g.durations) > DoppelGangerMaxDurations {
101 130
 				copy(g.durations, g.durations[len(g.durations)-DoppelGangerMaxDurations:])
102 131
 				g.durations = g.durations[:DoppelGangerMaxDurations]
103 132
 			}
104 133
 
134
+			// Update cert sizes and recompute noise params.
135
+			g.certSizes = append(g.certSizes, result.certSizes...)
136
+			if len(g.certSizes) > DoppelGangerMaxDurations {
137
+				g.certSizes = g.certSizes[len(g.certSizes)-DoppelGangerMaxDurations:]
138
+			}
139
+
140
+			if len(g.certSizes) >= MinCertSizesToCalculate {
141
+				g.updateNoiseParams()
142
+			}
143
+
105 144
 			if len(g.durations) < MinDurationsToCalculate {
106 145
 				continue
107 146
 			}
108 147
 
148
+			durations := g.durations
109 149
 			currentScoutCollectedChan = nil
110 150
 			g.wg.Go(func() {
111 151
 				select {
@@ -129,8 +169,45 @@ func (g *Ganger) run() {
129 169
 	}
130 170
 }
131 171
 
132
-func (g *Ganger) runScoutRaid(rvChan chan<- []time.Duration) {
133
-	durations := []time.Duration{}
172
+func (g *Ganger) updateNoiseParams() {
173
+	if len(g.certSizes) == 0 {
174
+		return
175
+	}
176
+
177
+	sum := 0
178
+	for _, s := range g.certSizes {
179
+		sum += s
180
+	}
181
+
182
+	mean := sum / len(g.certSizes)
183
+
184
+	maxDev := 0
185
+	for _, s := range g.certSizes {
186
+		d := s - mean
187
+		if d < 0 {
188
+			d = -d
189
+		}
190
+
191
+		if d > maxDev {
192
+			maxDev = d
193
+		}
194
+	}
195
+
196
+	if maxDev < 100 {
197
+		maxDev = 100
198
+	}
199
+
200
+	np := &NoiseParams{Mean: mean, Jitter: maxDev}
201
+	g.noiseParams.Store(np)
202
+
203
+	g.logger.Info(fmt.Sprintf(
204
+		"updated noise params: mean=%d jitter=%d samples=%d",
205
+		mean, maxDev, len(g.certSizes),
206
+	))
207
+}
208
+
209
+func (g *Ganger) runScoutRaid(rvChan chan<- scoutRaidResult) {
210
+	var result scoutRaidResult
134 211
 
135 212
 	for range g.scoutRaidRepeats {
136 213
 		learned, err := g.scout.Learn(g.ctx)
@@ -138,13 +215,18 @@ func (g *Ganger) runScoutRaid(rvChan chan<- []time.Duration) {
138 215
 			g.logger.WarningError("cannot learn", err)
139 216
 			continue
140 217
 		}
141
-		durations = append(durations, learned...)
218
+
219
+		result.durations = append(result.durations, learned.Durations...)
220
+
221
+		if learned.CertSize > 0 {
222
+			result.certSizes = append(result.certSizes, learned.CertSize)
223
+		}
142 224
 	}
143 225
 
144 226
 	select {
145 227
 	case <-g.ctx.Done():
146 228
 		return
147
-	case rvChan <- durations:
229
+	case rvChan <- result:
148 230
 	}
149 231
 }
150 232
 

+ 47
- 12
mtglib/internal/doppel/scout.go Zobrazit soubor

@@ -12,36 +12,46 @@ import (
12 12
 	"github.com/9seconds/mtg/v2/mtglib/internal/tls"
13 13
 )
14 14
 
15
+// ScoutResult holds measurements from a single scout HTTP request.
16
+type ScoutResult struct {
17
+	Durations []time.Duration
18
+	CertSize  int // total ApplicationData bytes during TLS handshake; 0 if unknown
19
+}
20
+
15 21
 type Scout struct {
16 22
 	network Network
17 23
 	urls    []string
18 24
 }
19 25
 
20
-func (s Scout) Learn(ctx context.Context) ([]time.Duration, error) {
21
-	var durations []time.Duration
26
+func (s Scout) Learn(ctx context.Context) (ScoutResult, error) {
27
+	var combined ScoutResult
22 28
 
23 29
 	for _, url := range s.urls {
24 30
 		learned, err := s.learn(ctx, url)
25 31
 		if err != nil {
26
-			return nil, err
32
+			return ScoutResult{}, err
27 33
 		}
28 34
 
29
-		durations = append(durations, learned...)
35
+		combined.Durations = append(combined.Durations, learned.Durations...)
36
+
37
+		if learned.CertSize > 0 && combined.CertSize == 0 {
38
+			combined.CertSize = learned.CertSize
39
+		}
30 40
 	}
31 41
 
32
-	return durations, nil
42
+	return combined, nil
33 43
 }
34 44
 
35
-func (s Scout) learn(ctx context.Context, url string) ([]time.Duration, error) {
45
+func (s Scout) learn(ctx context.Context, url string) (ScoutResult, error) {
36 46
 	client, results := s.makeClient()
37 47
 
38 48
 	if !strings.HasPrefix(url, "https://") {
39
-		return nil, fmt.Errorf("url %s must be https", url)
49
+		return ScoutResult{}, fmt.Errorf("url %s must be https", url)
40 50
 	}
41 51
 
42 52
 	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
43 53
 	if err != nil {
44
-		return nil, err
54
+		return ScoutResult{}, err
45 55
 	}
46 56
 
47 57
 	resp, err := client.Do(req)
@@ -52,10 +62,12 @@ func (s Scout) learn(ctx context.Context, url string) ([]time.Duration, error) {
52 62
 	}
53 63
 
54 64
 	if err != nil || len(results.data) == 0 {
55
-		return nil, err
65
+		return ScoutResult{}, err
56 66
 	}
57 67
 
58
-	durations := []time.Duration{}
68
+	var result ScoutResult
69
+
70
+	// Compute inter-record durations (existing logic).
59 71
 	lastTimestamp := time.Time{}
60 72
 
61 73
 	for i, v := range results.data {
@@ -71,11 +83,34 @@ func (s Scout) learn(ctx context.Context, url string) ([]time.Duration, error) {
71 83
 			}
72 84
 		}
73 85
 
74
-		durations = append(durations, v.timestamp.Sub(lastTimestamp))
86
+		result.Durations = append(result.Durations, v.timestamp.Sub(lastTimestamp))
75 87
 		lastTimestamp = v.timestamp
76 88
 	}
77 89
 
78
-	return durations, nil
90
+	// Compute cert size: sum of ApplicationData payload between CCS and
91
+	// the first client Write (which marks the end of server handshake).
92
+	seenCCS := false
93
+	boundary := results.writeIndex
94
+	if boundary < 0 {
95
+		boundary = len(results.data)
96
+	}
97
+
98
+	for i, v := range results.data {
99
+		if i >= boundary {
100
+			break
101
+		}
102
+
103
+		if v.recordType == tls.TypeChangeCipherSpec {
104
+			seenCCS = true
105
+			continue
106
+		}
107
+
108
+		if seenCCS && v.recordType == tls.TypeApplicationData {
109
+			result.CertSize += v.payloadLen
110
+		}
111
+	}
112
+
113
+	return result, nil
79 114
 }
80 115
 
81 116
 func (s Scout) makeClient() (*http.Client, *ScoutConnCollected) {

+ 17
- 4
mtglib/internal/doppel/scout_conn.go Zobrazit soubor

@@ -14,9 +14,10 @@ type ScoutConn struct {
14 14
 
15 15
 	results *ScoutConnCollected
16 16
 	rawBuf  *bytes.Buffer
17
+	seenCCS bool
17 18
 }
18 19
 
19
-func (s ScoutConn) Read(p []byte) (int, error) {
20
+func (s *ScoutConn) Read(p []byte) (int, error) {
20 21
 	buf := &bytes.Buffer{}
21 22
 
22 23
 	for {
@@ -31,7 +32,11 @@ func (s ScoutConn) Read(p []byte) (int, error) {
31 32
 			return 0, err
32 33
 		}
33 34
 
34
-		s.results.Add(recordType)
35
+		if recordType == tls.TypeChangeCipherSpec {
36
+			s.seenCCS = true
37
+		}
38
+
39
+		s.results.Add(recordType, int(length))
35 40
 		s.rawBuf.Write([]byte{recordType})
36 41
 		s.rawBuf.Write(tls.TLSVersion[:])
37 42
 
@@ -45,11 +50,19 @@ func (s ScoutConn) Read(p []byte) (int, error) {
45 50
 	}
46 51
 }
47 52
 
48
-func NewScoutConn(conn essentials.Conn, results *ScoutConnCollected) ScoutConn {
53
+func (s *ScoutConn) Write(p []byte) (int, error) {
54
+	if s.seenCCS {
55
+		s.results.MarkWrite()
56
+	}
57
+
58
+	return s.Conn.Write(p)
59
+}
60
+
61
+func NewScoutConn(conn essentials.Conn, results *ScoutConnCollected) *ScoutConn {
49 62
 	rawBuf := &bytes.Buffer{}
50 63
 	rawBuf.Grow(tls.MaxRecordSize)
51 64
 
52
-	return ScoutConn{
65
+	return &ScoutConn{
53 66
 		Conn:    tls.New(conn, false, false),
54 67
 		results: results,
55 68
 		rawBuf:  rawBuf,

+ 14
- 3
mtglib/internal/doppel/scout_conn_collected.go Zobrazit soubor

@@ -9,21 +9,32 @@ const (
9 9
 type ScoutConnResult struct {
10 10
 	timestamp  time.Time
11 11
 	recordType byte
12
+	payloadLen int
12 13
 }
13 14
 
14 15
 type ScoutConnCollected struct {
15
-	data []ScoutConnResult
16
+	data       []ScoutConnResult
17
+	writeIndex int // index at which client first wrote post-handshake data; -1 if not set
16 18
 }
17 19
 
18
-func (s *ScoutConnCollected) Add(record byte) {
20
+func (s *ScoutConnCollected) Add(record byte, payloadLen int) {
19 21
 	s.data = append(s.data, ScoutConnResult{
20 22
 		timestamp:  time.Now(),
21 23
 		recordType: record,
24
+		payloadLen: payloadLen,
22 25
 	})
23 26
 }
24 27
 
28
+// MarkWrite records the current data length as the handshake boundary.
29
+func (s *ScoutConnCollected) MarkWrite() {
30
+	if s.writeIndex < 0 {
31
+		s.writeIndex = len(s.data)
32
+	}
33
+}
34
+
25 35
 func NewScoutConnCollected() *ScoutConnCollected {
26 36
 	return &ScoutConnCollected{
27
-		data: make([]ScoutConnResult, 0, ScoutConnCollectedPreallocSize),
37
+		data:       make([]ScoutConnResult, 0, ScoutConnCollectedPreallocSize),
38
+		writeIndex: -1,
28 39
 	}
29 40
 }

+ 4
- 4
mtglib/internal/doppel/scout_conn_collected_test.go Zobrazit soubor

@@ -14,7 +14,7 @@ type ScoutConnCollectedTestSuite struct {
14 14
 
15 15
 func (suite *ScoutConnCollectedTestSuite) TestAddSingle() {
16 16
 	collected := NewScoutConnCollected()
17
-	collected.Add(tls.TypeApplicationData)
17
+	collected.Add(tls.TypeApplicationData, 100)
18 18
 
19 19
 	suite.Len(collected.data, 1)
20 20
 	suite.Equal(byte(tls.TypeApplicationData), collected.data[0].recordType)
@@ -23,13 +23,13 @@ func (suite *ScoutConnCollectedTestSuite) TestAddSingle() {
23 23
 func (suite *ScoutConnCollectedTestSuite) TestAddTimestampsAreMonotonic() {
24 24
 	collected := NewScoutConnCollected()
25 25
 
26
-	collected.Add(tls.TypeApplicationData)
26
+	collected.Add(tls.TypeApplicationData, 100)
27 27
 
28 28
 	time.Sleep(time.Microsecond)
29
-	collected.Add(tls.TypeApplicationData)
29
+	collected.Add(tls.TypeApplicationData, 100)
30 30
 
31 31
 	time.Sleep(time.Microsecond)
32
-	collected.Add(tls.TypeApplicationData)
32
+	collected.Add(tls.TypeApplicationData, 100)
33 33
 
34 34
 	for i := 1; i < len(collected.data); i++ {
35 35
 		suite.True(collected.data[i].timestamp.After(collected.data[i-1].timestamp))

+ 2
- 2
mtglib/internal/doppel/scout_test.go Zobrazit soubor

@@ -22,9 +22,9 @@ func (suite *ScoutTestSuite) SetupSuite() {
22 22
 }
23 23
 
24 24
 func (suite *ScoutTestSuite) TestCollectResults() {
25
-	durations, err := suite.scout.Learn(suite.ctx)
25
+	result, err := suite.scout.Learn(suite.ctx)
26 26
 	suite.NoError(err)
27
-	suite.Less(3, len(durations))
27
+	suite.Less(3, len(result.Durations))
28 28
 }
29 29
 
30 30
 func (suite *ScoutTestSuite) TestCollectNothing() {

+ 34
- 15
mtglib/internal/tls/fake/server_side.go Zobrazit soubor

@@ -9,11 +9,18 @@ import (
9 9
 	"io"
10 10
 	rnd "math/rand/v2"
11 11
 
12
-	"github.com/9seconds/mtg/v2/mtglib/internal/doppel"
13 12
 	"github.com/9seconds/mtg/v2/mtglib/internal/tls"
14 13
 	"golang.org/x/crypto/curve25519"
15 14
 )
16 15
 
16
+// NoiseParams controls the size of the fake ApplicationData record
17
+// in ServerHello. If Mean is 0, the legacy random range (2500-4700)
18
+// is used.
19
+type NoiseParams struct {
20
+	Mean   int
21
+	Jitter int
22
+}
23
+
17 24
 const (
18 25
 	TypeHandshakeServer = 0x02
19 26
 	ChangeCipherValue   = 0x01
@@ -33,13 +40,13 @@ var serverHelloSuffix = []byte{
33 40
 	0x00, 0x20, // 32 bytes of key
34 41
 }
35 42
 
36
-func SendServerHello(w io.Writer, secret []byte, clientHello *ClientHello) error {
43
+func SendServerHello(w io.Writer, secret []byte, clientHello *ClientHello, noise NoiseParams) error {
37 44
 	buf := &bytes.Buffer{}
38 45
 	buf.Grow(tls.MaxRecordSize)
39 46
 
40 47
 	generateServerHello(buf, clientHello)
41 48
 	generateChangeCipherValue(buf)
42
-	generateNoise(buf)
49
+	generateNoise(buf, noise)
43 50
 
44 51
 	packet := buf.Bytes()
45 52
 	digest := hmac.New(sha256.New, secret)
@@ -125,19 +132,31 @@ func generateChangeCipherValue(buf *bytes.Buffer) {
125 132
 	buf.WriteByte(ChangeCipherValue)
126 133
 }
127 134
 
128
-func generateNoise(buf *bytes.Buffer) {
129
-	data := make(
130
-		[]byte,
131
-		int64(
132
-			doppel.TLSRecordSizeStart+rnd.IntN(
133
-				doppel.TLSRecordSizeAccel-doppel.TLSRecordSizeStart,
134
-			),
135
-		),
136
-	)
137
-
138
-	if _, err := rand.Read(data[:]); err != nil {
135
+// generateNoise writes a single ApplicationData record mimicking the combined
136
+// size of a real TLS 1.3 encrypted server handshake (EncryptedExtensions +
137
+// Certificate chain + CertificateVerify + Finished).
138
+//
139
+// NOTE: Must be exactly ONE ApplicationData record — the Telegram client reads
140
+// ServerHello + CCS + 1 ApplicationData and computes HMAC over all three.
141
+// Multiple records would cause HMAC mismatch and connection failure.
142
+func generateNoise(buf *bytes.Buffer, noise NoiseParams) {
143
+	var size int
144
+
145
+	if noise.Mean > 0 && noise.Jitter > 0 {
146
+		// Calibrated: use measured cert chain size ± jitter.
147
+		size = noise.Mean - noise.Jitter + rnd.IntN(2*noise.Jitter)
148
+		if size < 1000 {
149
+			size = 1000
150
+		}
151
+	} else {
152
+		// Legacy fallback: random in 2500-4700 range.
153
+		size = 2500 + rnd.IntN(2200)
154
+	}
155
+
156
+	data := make([]byte, size)
157
+	if _, err := rand.Read(data); err != nil {
139 158
 		panic(err)
140 159
 	}
141 160
 
142
-	tls.WriteRecord(buf, data[:]) //nolint: errcheck
161
+	tls.WriteRecord(buf, data) //nolint: errcheck
143 162
 }

+ 32
- 6
mtglib/internal/tls/fake/server_side_test.go Zobrazit soubor

@@ -8,7 +8,6 @@ import (
8 8
 	"testing"
9 9
 
10 10
 	"github.com/9seconds/mtg/v2/mtglib"
11
-	"github.com/9seconds/mtg/v2/mtglib/internal/doppel"
12 11
 	"github.com/9seconds/mtg/v2/mtglib/internal/tls"
13 12
 	"github.com/9seconds/mtg/v2/mtglib/internal/tls/fake"
14 13
 	"github.com/stretchr/testify/suite"
@@ -39,7 +38,7 @@ func (suite *SendServerHelloTestSuite) SetupTest() {
39 38
 }
40 39
 
41 40
 func (suite *SendServerHelloTestSuite) TestRecordStructure() {
42
-	err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello)
41
+	err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello, fake.NoiseParams{})
43 42
 	suite.NoError(err)
44 43
 
45 44
 	var rec bytes.Buffer
@@ -59,13 +58,13 @@ func (suite *SendServerHelloTestSuite) TestRecordStructure() {
59 58
 	recordType, length, err := tls.ReadRecord(suite.buf, &rec)
60 59
 	suite.NoError(err)
61 60
 	suite.Equal(byte(tls.TypeApplicationData), recordType)
62
-	suite.Greater(length, int64(doppel.TLSRecordSizeStart))
61
+	suite.Greater(length, int64(2500))
63 62
 
64 63
 	suite.Empty(suite.buf.Bytes())
65 64
 }
66 65
 
67 66
 func (suite *SendServerHelloTestSuite) TestHMAC() {
68
-	err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello)
67
+	err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello, fake.NoiseParams{})
69 68
 	suite.NoError(err)
70 69
 
71 70
 	packet := make([]byte, suite.buf.Len())
@@ -83,7 +82,7 @@ func (suite *SendServerHelloTestSuite) TestHMAC() {
83 82
 }
84 83
 
85 84
 func (suite *SendServerHelloTestSuite) TestHandshakePayload() {
86
-	err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello)
85
+	err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello, fake.NoiseParams{})
87 86
 	suite.NoError(err)
88 87
 
89 88
 	packet := suite.buf.Bytes()
@@ -105,7 +104,7 @@ func (suite *SendServerHelloTestSuite) TestHandshakePayload() {
105 104
 }
106 105
 
107 106
 func (suite *SendServerHelloTestSuite) TestChangeCipherSpec() {
108
-	err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello)
107
+	err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello, fake.NoiseParams{})
109 108
 	suite.NoError(err)
110 109
 
111 110
 	// Skip first record
@@ -124,6 +123,33 @@ func (suite *SendServerHelloTestSuite) TestChangeCipherSpec() {
124 123
 	suite.Equal([]byte{fake.ChangeCipherValue}, rec.Bytes())
125 124
 }
126 125
 
126
+func (suite *SendServerHelloTestSuite) TestCalibratedNoiseSize() {
127
+	noise := fake.NoiseParams{Mean: 6480, Jitter: 100}
128
+	err := fake.SendServerHello(suite.buf, suite.secret.Key[:], suite.hello, noise)
129
+	suite.NoError(err)
130
+
131
+	var rec bytes.Buffer
132
+
133
+	// Skip ServerHello
134
+	_, _, err = tls.ReadRecord(suite.buf, &rec)
135
+	suite.NoError(err)
136
+
137
+	// Skip ChangeCipherSpec
138
+	rec.Reset()
139
+	_, _, err = tls.ReadRecord(suite.buf, &rec)
140
+	suite.NoError(err)
141
+
142
+	// Read noise ApplicationData
143
+	rec.Reset()
144
+	recordType, length, err := tls.ReadRecord(suite.buf, &rec)
145
+	suite.NoError(err)
146
+	suite.Equal(byte(tls.TypeApplicationData), recordType)
147
+
148
+	// Should be within mean ± jitter range.
149
+	suite.GreaterOrEqual(length, int64(noise.Mean-noise.Jitter))
150
+	suite.LessOrEqual(length, int64(noise.Mean+noise.Jitter))
151
+}
152
+
127 153
 func TestSendServerHello(t *testing.T) {
128 154
 	t.Parallel()
129 155
 	suite.Run(t, &SendServerHelloTestSuite{})

+ 4
- 1
mtglib/proxy.go Zobrazit soubor

@@ -192,7 +192,10 @@ func (p *Proxy) doFakeTLSHandshake(ctx *streamContext) bool {
192 192
 		return false
193 193
 	}
194 194
 
195
-	if err := fake.SendServerHello(ctx.clientConn, p.secret.Key[:], clientHello); err != nil {
195
+	gangerNoise := p.doppelGanger.NoiseParams()
196
+	noiseParams := fake.NoiseParams{Mean: gangerNoise.Mean, Jitter: gangerNoise.Jitter}
197
+
198
+	if err := fake.SendServerHello(ctx.clientConn, p.secret.Key[:], clientHello, noiseParams); err != nil {
196 199
 		p.logger.InfoError("cannot send welcome packet", err)
197 200
 		return false
198 201
 	}

+ 1
- 0
mtglib/proxy_opts.go Zobrazit soubor

@@ -160,6 +160,7 @@ type ProxyOpts struct {
160 160
 
161 161
 	// DoppelGangerDRS defines if TLS Dynamic Record Sizing is active.
162 162
 	DoppelGangerDRS bool
163
+
163 164
 }
164 165
 
165 166
 func (p ProxyOpts) valid() error {

Načítá se…
Zrušit
Uložit