Ver código fonte

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 4 semanas atrás
pai
commit
74a81a986a
Nenhuma conta vinculada ao e-mail do autor do commit

+ 15
- 8
mtglib/internal/tls/fake/client_side.go Ver arquivo

@@ -130,18 +130,25 @@ func parseHandshake(r io.Reader) (*ClientHello, error) {
130 130
 
131 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 152
 	if _, err := io.ReadFull(r, header[:1]); err != nil {
146 153
 		return nil, fmt.Errorf("cannot read compression methods length: %w", err)
147 154
 	}

+ 4
- 3
mtglib/internal/tls/fake/client_side_test.go Ver arquivo

@@ -234,12 +234,13 @@ func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotReadCipherSuiteLe
234 234
 }
235 235
 
236 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 240
 	suite.writeBody(body)
240 241
 
241 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 246
 func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotSkipRemainingCipherSuites() {
@@ -249,7 +250,7 @@ func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotSkipRemainingCiph
249 250
 	suite.writeBody(body)
250 251
 
251 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 256
 func (suite *ParseClientHelloHandshakeBodyTestSuite) TestCannotReadCompressionMethodsLength() {

+ 1
- 1
mtglib/internal/tls/fake/server_side_test.go Ver arquivo

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

+ 8
- 0
mtglib/internal/tls/fake/testdata/client-hello-ok-grease-first.json Ver arquivo

@@ -0,0 +1,8 @@
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
+}

Carregando…
Cancelar
Salvar