Przeglądaj źródła

Merge pull request #446 from runixer/fix/grease-cipher-suite

Fix DPI detection: replace GREASE cipher suite in FakeTLS ServerHello
tags/v2.2.8^2^2
Sergei Arkhipov 1 miesiąc temu
rodzic
commit
74a81a986a
No account linked to committer's email address

+ 15
- 8
mtglib/internal/tls/fake/client_side.go Wyświetl plik

130
 
130
 
131
 	cipherSuiteLen := int64(binary.BigEndian.Uint16(header[:]))
131
 	cipherSuiteLen := int64(binary.BigEndian.Uint16(header[:]))
132
 
132
 
133
-	// we do not care about picking up any cipher. we pick the first one,
134
-	// so it is always should be present.
135
-	if _, err := io.ReadFull(r, header[:]); err != nil {
136
-		return nil, fmt.Errorf("cannot read first cipher suite: %w", err)
137
-	}
133
+	// Pick the first non-GREASE cipher suite from the list.
134
+	// Real TLS servers never select GREASE values (RFC 8701, pattern 0x?a?a),
135
+	// so echoing them back is a trivial DPI fingerprint.
136
+	for remaining := cipherSuiteLen; remaining >= 2; remaining -= 2 {
137
+		if _, err := io.ReadFull(r, header[:]); err != nil {
138
+			return nil, fmt.Errorf("cannot read cipher suite: %w", err)
139
+		}
138
 
140
 
139
-	hello.CipherSuite = binary.BigEndian.Uint16(header[:])
141
+		cs := binary.BigEndian.Uint16(header[:])
142
+		if hello.CipherSuite == 0 && cs&0x0f0f != 0x0a0a {
143
+			hello.CipherSuite = cs
144
+		}
145
+	}
140
 
146
 
141
-	if _, err := io.CopyN(io.Discard, r, cipherSuiteLen-2); err != nil {
142
-		return nil, fmt.Errorf("cannot skip remaining cipher suites: %w", err)
147
+	if hello.CipherSuite == 0 {
148
+		hello.CipherSuite = 0x1301 // fallback: TLS_AES_128_GCM_SHA256
143
 	}
149
 	}
144
 
150
 
151
+
145
 	if _, err := io.ReadFull(r, header[:1]); err != nil {
152
 	if _, err := io.ReadFull(r, header[:1]); err != nil {
146
 		return nil, fmt.Errorf("cannot read compression methods length: %w", err)
153
 		return nil, fmt.Errorf("cannot read compression methods length: %w", err)
147
 	}
154
 	}

+ 4
- 3
mtglib/internal/tls/fake/client_side_test.go Wyświetl plik

234
 }
234
 }
235
 
235
 
236
 func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotReadFirstCipherSuite() {
236
 func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotReadFirstCipherSuite() {
237
-	body := make([]byte, 2+fake.RandomLen+1+2)
237
+	body := make([]byte, 2+fake.RandomLen+1+2+1) // cipherSuiteLen=2 but only 1 byte available
238
+	binary.BigEndian.PutUint16(body[2+fake.RandomLen+1:], 2)
238
 
239
 
239
 	suite.writeBody(body)
240
 	suite.writeBody(body)
240
 
241
 
241
 	_, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime)
242
 	_, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime)
242
-	suite.ErrorContains(err, "cannot read first cipher suite")
243
+	suite.ErrorContains(err, "cannot read cipher suite")
243
 }
244
 }
244
 
245
 
245
 func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotSkipRemainingCipherSuites() {
246
 func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotSkipRemainingCipherSuites() {
249
 	suite.writeBody(body)
250
 	suite.writeBody(body)
250
 
251
 
251
 	_, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime)
252
 	_, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime)
252
-	suite.ErrorContains(err, "cannot skip remaining cipher suites")
253
+	suite.ErrorContains(err, "cannot read cipher suite")
253
 }
254
 }
254
 
255
 
255
 func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotReadCompressionMethodsLength() {
256
 func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotReadCompressionMethodsLength() {

+ 1
- 1
mtglib/internal/tls/fake/server_side_test.go Wyświetl plik

58
 	recordType, length, err := tls.ReadRecord(suite.buf, &rec)
58
 	recordType, length, err := tls.ReadRecord(suite.buf, &rec)
59
 	suite.NoError(err)
59
 	suite.NoError(err)
60
 	suite.Equal(byte(tls.TypeApplicationData), recordType)
60
 	suite.Equal(byte(tls.TypeApplicationData), recordType)
61
-	suite.Greater(length, int64(2500))
61
+	suite.GreaterOrEqual(length, int64(2500))
62
 
62
 
63
 	suite.Empty(suite.buf.Bytes())
63
 	suite.Empty(suite.buf.Bytes())
64
 }
64
 }

+ 8
- 0
mtglib/internal/tls/fake/testdata/client-hello-ok-grease-first.json Wyświetl plik

1
+{
2
+  "time": 1617181365,
3
+  "random": "w4TaDfYg/aUKdx1oi68vxMKvHJczRNvtRRppLETzeNE=",
4
+  "sessionId": "St2BZ2uHMFn3B2trD1jfdtpjoJOOg6JBeLhFcyCMCq4=",
5
+  "host": "storage.googleapis.com",
6
+  "cipherSuite": 4867,
7
+  "full": "FgMBAgIBAAH+AwPDhNoN9iD9pQp3HWiLry/Ewq8clzNE2+1FGmksRPN40SBK3YFna4cwWfcHa2sPWN922mOgk46DokF4uEVzIIwKrgA2WloTAxMBEwLALMArwCTAI8AKwAnMqcAwwC/AKMAnwBTAE8yoAJ0AnAA9ADwANQAvwAjAEgAKAQABf/8BAAEAAAAAGwAZAAAWc3RvcmFnZS5nb29nbGVhcGlzLmNvbQAXAAAADQAYABYEAwgEBAEFAwIDCAUIBQUBCAYGAQIBAAUABQEAAAAAM3QAAAASAAAAEAAwAC4CaDIFaDItMTYFaDItMTUFaDItMTQIc3BkeS8zLjEGc3BkeS8zCGh0dHAvMS4xAAsAAgEAADMAJgAkAB0AIAf+6C8fSRJSAC7CyUvdR9kDclNR9KLCsCFHpVZ3bC8iAC0AAgEBACsACQgDBAMDAwIDAQAKAAoACAAdABcAGAAZABUAoQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
8
+}

Ładowanie…
Anuluj
Zapisz