|
|
@@ -5,6 +5,7 @@ import (
|
|
5
|
5
|
"crypto/hmac"
|
|
6
|
6
|
"crypto/sha256"
|
|
7
|
7
|
"crypto/subtle"
|
|
|
8
|
+ "crypto/tls"
|
|
8
|
9
|
"encoding/binary"
|
|
9
|
10
|
"fmt"
|
|
10
|
11
|
"io"
|
|
|
@@ -20,6 +21,12 @@ const (
|
|
20
|
21
|
// record_type(1) + version(2) + size(2) + handshake_type(1) + uint24_length(3) + client_version(2)
|
|
21
|
22
|
RandomOffset = 1 + 2 + 2 + 1 + 3 + 2
|
|
22
|
23
|
|
|
|
24
|
+ // https://datatracker.ietf.org/doc/html/rfc8701#name-grease-values
|
|
|
25
|
+ // https://medium.com/asecuritysite-when-bob-met-alice/in-cybersecurity-what-is-grease-9f8850558dea
|
|
|
26
|
+ GreaseMask = 0x0f0f
|
|
|
27
|
+ GreaseValueType = 0x0a0a
|
|
|
28
|
+ DefaultCipher = tls.TLS_AES_128_GCM_SHA256
|
|
|
29
|
+
|
|
23
|
30
|
sniDNSNamesListType = 0
|
|
24
|
31
|
)
|
|
25
|
32
|
|
|
|
@@ -108,7 +115,9 @@ func parseHandshake(r io.Reader) (*ClientHello, error) {
|
|
108
|
115
|
return nil, fmt.Errorf("cannot read client version: %w", err)
|
|
109
|
116
|
}
|
|
110
|
117
|
|
|
111
|
|
- hello := &ClientHello{}
|
|
|
118
|
+ hello := &ClientHello{
|
|
|
119
|
+ CipherSuite: DefaultCipher,
|
|
|
120
|
+ }
|
|
112
|
121
|
|
|
113
|
122
|
if _, err := io.ReadFull(r, hello.Random[:]); err != nil {
|
|
114
|
123
|
return nil, fmt.Errorf("cannot read client random: %w", err)
|
|
|
@@ -129,26 +138,28 @@ func parseHandshake(r io.Reader) (*ClientHello, error) {
|
|
129
|
138
|
}
|
|
130
|
139
|
|
|
131
|
140
|
cipherSuiteLen := int64(binary.BigEndian.Uint16(header[:]))
|
|
|
141
|
+ foundCipher := false
|
|
132
|
142
|
|
|
133
|
143
|
// Pick the first non-GREASE cipher suite from the list.
|
|
134
|
144
|
// Real TLS servers never select GREASE values (RFC 8701, pattern 0x?a?a),
|
|
135
|
145
|
// so echoing them back is a trivial DPI fingerprint.
|
|
136
|
|
- for remaining := cipherSuiteLen; remaining >= 2; remaining -= 2 {
|
|
|
146
|
+ // cipherSuiteLen is in bytes; each cipher suite is 2 bytes.
|
|
|
147
|
+ for range cipherSuiteLen / 2 {
|
|
137
|
148
|
if _, err := io.ReadFull(r, header[:]); err != nil {
|
|
138
|
149
|
return nil, fmt.Errorf("cannot read cipher suite: %w", err)
|
|
139
|
150
|
}
|
|
140
|
151
|
|
|
141
|
|
- cs := binary.BigEndian.Uint16(header[:])
|
|
142
|
|
- if hello.CipherSuite == 0 && cs&0x0f0f != 0x0a0a {
|
|
143
|
|
- hello.CipherSuite = cs
|
|
|
152
|
+ if foundCipher {
|
|
|
153
|
+ continue
|
|
144
|
154
|
}
|
|
145
|
|
- }
|
|
146
|
155
|
|
|
147
|
|
- if hello.CipherSuite == 0 {
|
|
148
|
|
- hello.CipherSuite = 0x1301 // fallback: TLS_AES_128_GCM_SHA256
|
|
|
156
|
+ if cs := binary.BigEndian.Uint16(header[:]); cs&GreaseMask != GreaseValueType {
|
|
|
157
|
+ hello.CipherSuite = cs
|
|
|
158
|
+ // do not forget we have to scan until the end
|
|
|
159
|
+ foundCipher = true
|
|
|
160
|
+ }
|
|
149
|
161
|
}
|
|
150
|
162
|
|
|
151
|
|
-
|
|
152
|
163
|
if _, err := io.ReadFull(r, header[:1]); err != nil {
|
|
153
|
164
|
return nil, fmt.Errorf("cannot read compression methods length: %w", err)
|
|
154
|
165
|
}
|