File: u_fingerprinter.go

package info (click to toggle)
golang-refraction-networking-utls 1.2.1-3.1
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 3,072 kB
  • sloc: asm: 67; makefile: 5
file content (430 lines) | stat: -rw-r--r-- 16,687 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
// Copyright 2017 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package tls

import (
	"errors"
	"fmt"
	"strings"

	"golang.org/x/crypto/cryptobyte"
)

// Fingerprinter is a struct largely for holding options for the FingerprintClientHello func
type Fingerprinter struct {
	// KeepPSK will ensure that the PreSharedKey extension is passed along into the resulting ClientHelloSpec as-is
	KeepPSK bool
	// AllowBluntMimicry will ensure that unknown extensions are
	// passed along into the resulting ClientHelloSpec as-is
	// It will not ensure that the PSK is passed along, if you require that, use KeepPSK
	// WARNING: there could be numerous subtle issues with ClientHelloSpecs
	// that are generated with this flag which could compromise security and/or mimicry
	AllowBluntMimicry bool
	// AlwaysAddPadding will always add a UtlsPaddingExtension with BoringPaddingStyle
	// at the end of the extensions list if it isn't found in the fingerprinted hello.
	// This could be useful in scenarios where the hello you are fingerprinting does not
	// have any padding, but you suspect that other changes you make to the final hello
	// (including things like different SNI lengths) would cause padding to be necessary
	AlwaysAddPadding bool
}

// FingerprintClientHello returns a ClientHelloSpec which is based on the
// ClientHello that is passed in as the data argument
//
// If the ClientHello passed in has extensions that are not recognized or cannot be handled
// it will return a non-nil error and a nil *ClientHelloSpec value
//
// The data should be the full tls record, including the record type/version/length header
// as well as the handshake type/length/version header
// https://tools.ietf.org/html/rfc5246#section-6.2
// https://tools.ietf.org/html/rfc5246#section-7.4
func (f *Fingerprinter) FingerprintClientHello(data []byte) (*ClientHelloSpec, error) {
	clientHelloSpec := &ClientHelloSpec{}
	s := cryptobyte.String(data)

	var contentType uint8
	var recordVersion uint16
	if !s.ReadUint8(&contentType) || // record type
		!s.ReadUint16(&recordVersion) || !s.Skip(2) { // record version and length
		return nil, errors.New("unable to read record type, version, and length")
	}

	if recordType(contentType) != recordTypeHandshake {
		return nil, errors.New("record is not a handshake")
	}

	var handshakeVersion uint16
	var handshakeType uint8

	if !s.ReadUint8(&handshakeType) || !s.Skip(3) || // message type and 3 byte length
		!s.ReadUint16(&handshakeVersion) || !s.Skip(32) { // 32 byte random
		return nil, errors.New("unable to read handshake message type, length, and random")
	}

	if handshakeType != typeClientHello {
		return nil, errors.New("handshake message is not a ClientHello")
	}

	clientHelloSpec.TLSVersMin = recordVersion
	clientHelloSpec.TLSVersMax = handshakeVersion

	var ignoredSessionID cryptobyte.String
	if !s.ReadUint8LengthPrefixed(&ignoredSessionID) {
		return nil, errors.New("unable to read session id")
	}

	var cipherSuitesBytes cryptobyte.String
	if !s.ReadUint16LengthPrefixed(&cipherSuitesBytes) {
		return nil, errors.New("unable to read ciphersuites")
	}
	cipherSuites := []uint16{}
	for !cipherSuitesBytes.Empty() {
		var suite uint16
		if !cipherSuitesBytes.ReadUint16(&suite) {
			return nil, errors.New("unable to read ciphersuite")
		}
		cipherSuites = append(cipherSuites, unGREASEUint16(suite))
	}
	clientHelloSpec.CipherSuites = cipherSuites

	if !readUint8LengthPrefixed(&s, &clientHelloSpec.CompressionMethods) {
		return nil, errors.New("unable to read compression methods")
	}

	if s.Empty() {
		// ClientHello is optionally followed by extension data
		return clientHelloSpec, nil
	}

	var extensions cryptobyte.String
	if !s.ReadUint16LengthPrefixed(&extensions) {
		return nil, errors.New("unable to read extensions data")
	}

	for !extensions.Empty() {
		var extension uint16
		var extData cryptobyte.String
		if !extensions.ReadUint16(&extension) ||
			!extensions.ReadUint16LengthPrefixed(&extData) {
			return nil, errors.New("unable to read extension data")
		}

		switch extension {
		case extensionServerName:
			// RFC 6066, Section 3
			var nameList cryptobyte.String
			if !extData.ReadUint16LengthPrefixed(&nameList) || nameList.Empty() {
				return nil, errors.New("unable to read server name extension data")
			}
			var serverName string
			for !nameList.Empty() {
				var nameType uint8
				var serverNameBytes cryptobyte.String
				if !nameList.ReadUint8(&nameType) ||
					!nameList.ReadUint16LengthPrefixed(&serverNameBytes) ||
					serverNameBytes.Empty() {
					return nil, errors.New("unable to read server name extension data")
				}
				if nameType != 0 {
					continue
				}
				if len(serverName) != 0 {
					return nil, errors.New("multiple names of the same name_type in server name extension are prohibited")
				}
				serverName = string(serverNameBytes)
				if strings.HasSuffix(serverName, ".") {
					return nil, errors.New("SNI value may not include a trailing dot")
				}

				clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SNIExtension{})

			}
		case extensionNextProtoNeg:
			// draft-agl-tls-nextprotoneg-04
			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &NPNExtension{})

		case extensionStatusRequest:
			// RFC 4366, Section 3.6
			var statusType uint8
			var ignored cryptobyte.String
			if !extData.ReadUint8(&statusType) ||
				!extData.ReadUint16LengthPrefixed(&ignored) ||
				!extData.ReadUint16LengthPrefixed(&ignored) {
				return nil, errors.New("unable to read status request extension data")
			}

			if statusType == statusTypeOCSP {
				clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &StatusRequestExtension{})
			} else {
				return nil, errors.New("status request extension statusType is not statusTypeOCSP")
			}

		case extensionSupportedCurves:
			// RFC 4492, sections 5.1.1 and RFC 8446, Section 4.2.7
			var curvesBytes cryptobyte.String
			if !extData.ReadUint16LengthPrefixed(&curvesBytes) || curvesBytes.Empty() {
				return nil, errors.New("unable to read supported curves extension data")
			}
			curves := []CurveID{}
			for !curvesBytes.Empty() {
				var curve uint16
				if !curvesBytes.ReadUint16(&curve) {
					return nil, errors.New("unable to read supported curves extension data")
				}
				curves = append(curves, CurveID(unGREASEUint16(curve)))
			}
			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SupportedCurvesExtension{curves})

		case extensionSupportedPoints:
			// RFC 4492, Section 5.1.2
			supportedPoints := []uint8{}
			if !readUint8LengthPrefixed(&extData, &supportedPoints) ||
				len(supportedPoints) == 0 {
				return nil, errors.New("unable to read supported points extension data")
			}
			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SupportedPointsExtension{supportedPoints})

		case extensionSessionTicket:
			// RFC 5077, Section 3.2
			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SessionTicketExtension{})

		case extensionSignatureAlgorithms:
			// RFC 5246, Section 7.4.1.4.1
			var sigAndAlgs cryptobyte.String
			if !extData.ReadUint16LengthPrefixed(&sigAndAlgs) || sigAndAlgs.Empty() {
				return nil, errors.New("unable to read signature algorithms extension data")
			}
			supportedSignatureAlgorithms := []SignatureScheme{}
			for !sigAndAlgs.Empty() {
				var sigAndAlg uint16
				if !sigAndAlgs.ReadUint16(&sigAndAlg) {
					return nil, errors.New("unable to read signature algorithms extension data")
				}
				supportedSignatureAlgorithms = append(
					supportedSignatureAlgorithms, SignatureScheme(sigAndAlg))
			}
			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SignatureAlgorithmsExtension{supportedSignatureAlgorithms})

		case extensionSignatureAlgorithmsCert:
			// RFC 8446, Section 4.2.3
			if f.AllowBluntMimicry {
				clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})
			} else {
				return nil, errors.New("unsupported extension SignatureAlgorithmsCert")
			}

		case extensionRenegotiationInfo:
			// RFC 5746, Section 3.2
			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &RenegotiationInfoExtension{RenegotiateOnceAsClient})

		case extensionALPN:
			// RFC 7301, Section 3.1
			var protoList cryptobyte.String
			if !extData.ReadUint16LengthPrefixed(&protoList) || protoList.Empty() {
				return nil, errors.New("unable to read ALPN extension data")
			}
			alpnProtocols := []string{}
			for !protoList.Empty() {
				var proto cryptobyte.String
				if !protoList.ReadUint8LengthPrefixed(&proto) || proto.Empty() {
					return nil, errors.New("unable to read ALPN extension data")
				}
				alpnProtocols = append(alpnProtocols, string(proto))

			}
			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &ALPNExtension{alpnProtocols})

		case extensionSCT:
			// RFC 6962, Section 3.3.1
			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SCTExtension{})

		case extensionSupportedVersions:
			// RFC 8446, Section 4.2.1
			var versList cryptobyte.String
			if !extData.ReadUint8LengthPrefixed(&versList) || versList.Empty() {
				return nil, errors.New("unable to read supported versions extension data")
			}
			supportedVersions := []uint16{}
			for !versList.Empty() {
				var vers uint16
				if !versList.ReadUint16(&vers) {
					return nil, errors.New("unable to read supported versions extension data")
				}
				supportedVersions = append(supportedVersions, unGREASEUint16(vers))
			}
			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SupportedVersionsExtension{supportedVersions})
			// If SupportedVersionsExtension is present, use that instead of record+handshake versions
			clientHelloSpec.TLSVersMin = 0
			clientHelloSpec.TLSVersMax = 0

		case extensionKeyShare:
			// RFC 8446, Section 4.2.8
			var clientShares cryptobyte.String
			if !extData.ReadUint16LengthPrefixed(&clientShares) {
				return nil, errors.New("unable to read key share extension data")
			}
			keyShares := []KeyShare{}
			for !clientShares.Empty() {
				var ks KeyShare
				var group uint16
				if !clientShares.ReadUint16(&group) ||
					!readUint16LengthPrefixed(&clientShares, &ks.Data) ||
					len(ks.Data) == 0 {
					return nil, errors.New("unable to read key share extension data")
				}
				ks.Group = CurveID(unGREASEUint16(group))
				// if not GREASE, key share data will be discarded as it should
				// be generated per connection
				if ks.Group != GREASE_PLACEHOLDER {
					ks.Data = nil
				}
				keyShares = append(keyShares, ks)
			}
			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &KeyShareExtension{keyShares})

		case extensionPSKModes:
			// RFC 8446, Section 4.2.9
			// TODO: PSK Modes have their own form of GREASE-ing which is not currently implemented
			// the current functionality will NOT re-GREASE/re-randomize these values when using a fingerprinted spec
			// https://github.com/refraction-networking/utls/pull/58#discussion_r522354105
			// https://tools.ietf.org/html/draft-ietf-tls-grease-01#section-2
			pskModes := []uint8{}
			if !readUint8LengthPrefixed(&extData, &pskModes) {
				return nil, errors.New("unable to read PSK extension data")
			}
			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &PSKKeyExchangeModesExtension{pskModes})

		case utlsExtensionExtendedMasterSecret:
			// https://tools.ietf.org/html/rfc7627
			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsExtendedMasterSecretExtension{})

		case utlsExtensionPadding:
			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle})

		case utlsExtensionCompressCertificate:
			methods := []CertCompressionAlgo{}
			methodsRaw := new(cryptobyte.String)
			if !extData.ReadUint8LengthPrefixed(methodsRaw) {
				return nil, errors.New("unable to read cert compression algorithms extension data")
			}
			for !methodsRaw.Empty() {
				var method uint16
				if !methodsRaw.ReadUint16(&method) {
					return nil, errors.New("unable to read cert compression algorithms extension data")
				}
				methods = append(methods, CertCompressionAlgo(method))
			}
			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsCompressCertExtension{methods})

		case fakeExtensionChannelID:
			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &FakeChannelIDExtension{})

		case fakeOldExtensionChannelID:
			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &FakeChannelIDExtension{true})

		case fakeExtensionTokenBinding:
			var tokenBindingExt FakeTokenBindingExtension
			var keyParameters cryptobyte.String
			if !extData.ReadUint8(&tokenBindingExt.MajorVersion) ||
				!extData.ReadUint8(&tokenBindingExt.MinorVersion) ||
				!extData.ReadUint8LengthPrefixed(&keyParameters) {
				return nil, errors.New("unable to read token binding extension data")
			}
			tokenBindingExt.KeyParameters = keyParameters
			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &tokenBindingExt)

		case utlsExtensionApplicationSettings:
			// Similar to ALPN (RFC 7301, Section 3.1):
			// https://datatracker.ietf.org/doc/html/draft-vvv-tls-alps#section-3
			var protoList cryptobyte.String
			if !extData.ReadUint16LengthPrefixed(&protoList) || protoList.Empty() {
				return nil, errors.New("unable to read ALPS extension data")
			}
			supportedProtocols := []string{}
			for !protoList.Empty() {
				var proto cryptobyte.String
				if !protoList.ReadUint8LengthPrefixed(&proto) || proto.Empty() {
					return nil, errors.New("unable to read ALPS extension data")
				}
				supportedProtocols = append(supportedProtocols, string(proto))
			}
			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &ApplicationSettingsExtension{supportedProtocols})

		case fakeRecordSizeLimit:
			recordSizeExt := new(FakeRecordSizeLimitExtension)
			if !extData.ReadUint16(&recordSizeExt.Limit) {
				return nil, errors.New("unable to read record size limit extension data")
			}
			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, recordSizeExt)

		case fakeExtensionDelegatedCredentials:
			//https://datatracker.ietf.org/doc/html/draft-ietf-tls-subcerts-15#section-4.1.1
			var supportedAlgs cryptobyte.String
			if !extData.ReadUint16LengthPrefixed(&supportedAlgs) || supportedAlgs.Empty() {
				return nil, errors.New("unable to read signature algorithms extension data")
			}
			supportedSignatureAlgorithms := []SignatureScheme{}
			for !supportedAlgs.Empty() {
				var sigAndAlg uint16
				if !supportedAlgs.ReadUint16(&sigAndAlg) {
					return nil, errors.New("unable to read signature algorithms extension data")
				}
				supportedSignatureAlgorithms = append(
					supportedSignatureAlgorithms, SignatureScheme(sigAndAlg))
			}
			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &FakeDelegatedCredentialsExtension{supportedSignatureAlgorithms})

		case extensionPreSharedKey:
			// RFC 8446, Section 4.2.11
			if f.KeepPSK {
				clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})
			} else {
				return nil, errors.New("unsupported extension PreSharedKey")
			}

		case extensionCookie:
			// RFC 8446, Section 4.2.2
			if f.AllowBluntMimicry {
				clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})
			} else {
				return nil, errors.New("unsupported extension Cookie")
			}

		case extensionEarlyData:
			// RFC 8446, Section 4.2.10
			if f.AllowBluntMimicry {
				clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})
			} else {
				return nil, errors.New("unsupported extension EarlyData")
			}

		default:
			if isGREASEUint16(extension) {
				clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsGREASEExtension{unGREASEUint16(extension), extData})
			} else if f.AllowBluntMimicry {
				clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})
			} else {
				return nil, fmt.Errorf("unsupported extension %d", extension)
			}

			continue
		}
	}

	if f.AlwaysAddPadding {
		alreadyHasPadding := false
		for _, ext := range clientHelloSpec.Extensions {
			if _, ok := ext.(*UtlsPaddingExtension); ok {
				alreadyHasPadding = true
				break
			}
		}
		if !alreadyHasPadding {
			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle})
		}
	}

	return clientHelloSpec, nil
}