1// Copyright 2017 Google Inc. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package tls
6
7import (
8	"errors"
9	"fmt"
10	"strings"
11
12	"golang.org/x/crypto/cryptobyte"
13)
14
15// Fingerprinter is a struct largely for holding options for the FingerprintClientHello func
16type Fingerprinter struct {
17	// KeepPSK will ensure that the PreSharedKey extension is passed along into the resulting ClientHelloSpec as-is
18	KeepPSK bool
19	// AllowBluntMimicry will ensure that unknown extensions are
20	// passed along into the resulting ClientHelloSpec as-is
21	// It will not ensure that the PSK is passed along, if you require that, use KeepPSK
22	// WARNING: there could be numerous subtle issues with ClientHelloSpecs
23	// that are generated with this flag which could compromise security and/or mimicry
24	AllowBluntMimicry bool
25	// AlwaysAddPadding will always add a UtlsPaddingExtension with BoringPaddingStyle
26	// at the end of the extensions list if it isn't found in the fingerprinted hello.
27	// This could be useful in scenarios where the hello you are fingerprinting does not
28	// have any padding, but you suspect that other changes you make to the final hello
29	// (including things like different SNI lengths) would cause padding to be necessary
30	AlwaysAddPadding bool
31}
32
33// FingerprintClientHello returns a ClientHelloSpec which is based on the
34// ClientHello that is passed in as the data argument
35//
36// If the ClientHello passed in has extensions that are not recognized or cannot be handled
37// it will return a non-nil error and a nil *ClientHelloSpec value
38//
39// The data should be the full tls record, including the record type/version/length header
40// as well as the handshake type/length/version header
41// https://tools.ietf.org/html/rfc5246#section-6.2
42// https://tools.ietf.org/html/rfc5246#section-7.4
43func (f *Fingerprinter) FingerprintClientHello(data []byte) (*ClientHelloSpec, error) {
44	clientHelloSpec := &ClientHelloSpec{}
45	s := cryptobyte.String(data)
46
47	var contentType uint8
48	var recordVersion uint16
49	if !s.ReadUint8(&contentType) || // record type
50		!s.ReadUint16(&recordVersion) || !s.Skip(2) { // record version and length
51		return nil, errors.New("unable to read record type, version, and length")
52	}
53
54	if recordType(contentType) != recordTypeHandshake {
55		return nil, errors.New("record is not a handshake")
56	}
57
58	var handshakeVersion uint16
59	var handshakeType uint8
60
61	if !s.ReadUint8(&handshakeType) || !s.Skip(3) || // message type and 3 byte length
62		!s.ReadUint16(&handshakeVersion) || !s.Skip(32) { // 32 byte random
63		return nil, errors.New("unable to read handshake message type, length, and random")
64	}
65
66	if handshakeType != typeClientHello {
67		return nil, errors.New("handshake message is not a ClientHello")
68	}
69
70	clientHelloSpec.TLSVersMin = recordVersion
71	clientHelloSpec.TLSVersMax = handshakeVersion
72
73	var ignoredSessionID cryptobyte.String
74	if !s.ReadUint8LengthPrefixed(&ignoredSessionID) {
75		return nil, errors.New("unable to read session id")
76	}
77
78	var cipherSuitesBytes cryptobyte.String
79	if !s.ReadUint16LengthPrefixed(&cipherSuitesBytes) {
80		return nil, errors.New("unable to read ciphersuites")
81	}
82	cipherSuites := []uint16{}
83	for !cipherSuitesBytes.Empty() {
84		var suite uint16
85		if !cipherSuitesBytes.ReadUint16(&suite) {
86			return nil, errors.New("unable to read ciphersuite")
87		}
88		cipherSuites = append(cipherSuites, unGREASEUint16(suite))
89	}
90	clientHelloSpec.CipherSuites = cipherSuites
91
92	if !readUint8LengthPrefixed(&s, &clientHelloSpec.CompressionMethods) {
93		return nil, errors.New("unable to read compression methods")
94	}
95
96	if s.Empty() {
97		// ClientHello is optionally followed by extension data
98		return clientHelloSpec, nil
99	}
100
101	var extensions cryptobyte.String
102	if !s.ReadUint16LengthPrefixed(&extensions) || !s.Empty() {
103		return nil, errors.New("unable to read extensions data")
104	}
105
106	for !extensions.Empty() {
107		var extension uint16
108		var extData cryptobyte.String
109		if !extensions.ReadUint16(&extension) ||
110			!extensions.ReadUint16LengthPrefixed(&extData) {
111			return nil, errors.New("unable to read extension data")
112		}
113
114		switch extension {
115		case extensionServerName:
116			// RFC 6066, Section 3
117			var nameList cryptobyte.String
118			if !extData.ReadUint16LengthPrefixed(&nameList) || nameList.Empty() {
119				return nil, errors.New("unable to read server name extension data")
120			}
121			var serverName string
122			for !nameList.Empty() {
123				var nameType uint8
124				var serverNameBytes cryptobyte.String
125				if !nameList.ReadUint8(&nameType) ||
126					!nameList.ReadUint16LengthPrefixed(&serverNameBytes) ||
127					serverNameBytes.Empty() {
128					return nil, errors.New("unable to read server name extension data")
129				}
130				if nameType != 0 {
131					continue
132				}
133				if len(serverName) != 0 {
134					return nil, errors.New("multiple names of the same name_type in server name extension are prohibited")
135				}
136				serverName = string(serverNameBytes)
137				if strings.HasSuffix(serverName, ".") {
138					return nil, errors.New("SNI value may not include a trailing dot")
139				}
140
141				clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SNIExtension{})
142
143			}
144		case extensionNextProtoNeg:
145			// draft-agl-tls-nextprotoneg-04
146			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &NPNExtension{})
147
148		case extensionStatusRequest:
149			// RFC 4366, Section 3.6
150			var statusType uint8
151			var ignored cryptobyte.String
152			if !extData.ReadUint8(&statusType) ||
153				!extData.ReadUint16LengthPrefixed(&ignored) ||
154				!extData.ReadUint16LengthPrefixed(&ignored) {
155				return nil, errors.New("unable to read status request extension data")
156			}
157
158			if statusType == statusTypeOCSP {
159				clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &StatusRequestExtension{})
160			} else {
161				return nil, errors.New("status request extension statusType is not statusTypeOCSP")
162			}
163
164		case extensionSupportedCurves:
165			// RFC 4492, sections 5.1.1 and RFC 8446, Section 4.2.7
166			var curvesBytes cryptobyte.String
167			if !extData.ReadUint16LengthPrefixed(&curvesBytes) || curvesBytes.Empty() {
168				return nil, errors.New("unable to read supported curves extension data")
169			}
170			curves := []CurveID{}
171			for !curvesBytes.Empty() {
172				var curve uint16
173				if !curvesBytes.ReadUint16(&curve) {
174					return nil, errors.New("unable to read supported curves extension data")
175				}
176				curves = append(curves, CurveID(unGREASEUint16(curve)))
177			}
178			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SupportedCurvesExtension{curves})
179
180		case extensionSupportedPoints:
181			// RFC 4492, Section 5.1.2
182			supportedPoints := []uint8{}
183			if !readUint8LengthPrefixed(&extData, &supportedPoints) ||
184				len(supportedPoints) == 0 {
185				return nil, errors.New("unable to read supported points extension data")
186			}
187			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SupportedPointsExtension{supportedPoints})
188
189		case extensionSessionTicket:
190			// RFC 5077, Section 3.2
191			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SessionTicketExtension{})
192
193		case extensionSignatureAlgorithms:
194			// RFC 5246, Section 7.4.1.4.1
195			var sigAndAlgs cryptobyte.String
196			if !extData.ReadUint16LengthPrefixed(&sigAndAlgs) || sigAndAlgs.Empty() {
197				return nil, errors.New("unable to read signature algorithms extension data")
198			}
199			supportedSignatureAlgorithms := []SignatureScheme{}
200			for !sigAndAlgs.Empty() {
201				var sigAndAlg uint16
202				if !sigAndAlgs.ReadUint16(&sigAndAlg) {
203					return nil, errors.New("unable to read signature algorithms extension data")
204				}
205				supportedSignatureAlgorithms = append(
206					supportedSignatureAlgorithms, SignatureScheme(sigAndAlg))
207			}
208			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SignatureAlgorithmsExtension{supportedSignatureAlgorithms})
209
210		case extensionSignatureAlgorithmsCert:
211			// RFC 8446, Section 4.2.3
212			if f.AllowBluntMimicry {
213				clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})
214			} else {
215				return nil, errors.New("unsupported extension SignatureAlgorithmsCert")
216			}
217
218		case extensionRenegotiationInfo:
219			// RFC 5746, Section 3.2
220			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &RenegotiationInfoExtension{RenegotiateOnceAsClient})
221
222		case extensionALPN:
223			// RFC 7301, Section 3.1
224			var protoList cryptobyte.String
225			if !extData.ReadUint16LengthPrefixed(&protoList) || protoList.Empty() {
226				return nil, errors.New("unable to read ALPN extension data")
227			}
228			alpnProtocols := []string{}
229			for !protoList.Empty() {
230				var proto cryptobyte.String
231				if !protoList.ReadUint8LengthPrefixed(&proto) || proto.Empty() {
232					return nil, errors.New("unable to read ALPN extension data")
233				}
234				alpnProtocols = append(alpnProtocols, string(proto))
235
236			}
237			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &ALPNExtension{alpnProtocols})
238
239		case extensionSCT:
240			// RFC 6962, Section 3.3.1
241			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SCTExtension{})
242
243		case extensionSupportedVersions:
244			// RFC 8446, Section 4.2.1
245			var versList cryptobyte.String
246			if !extData.ReadUint8LengthPrefixed(&versList) || versList.Empty() {
247				return nil, errors.New("unable to read supported versions extension data")
248			}
249			supportedVersions := []uint16{}
250			for !versList.Empty() {
251				var vers uint16
252				if !versList.ReadUint16(&vers) {
253					return nil, errors.New("unable to read supported versions extension data")
254				}
255				supportedVersions = append(supportedVersions, unGREASEUint16(vers))
256			}
257			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SupportedVersionsExtension{supportedVersions})
258			// If SupportedVersionsExtension is present, use that instead of record+handshake versions
259			clientHelloSpec.TLSVersMin = 0
260			clientHelloSpec.TLSVersMax = 0
261
262		case extensionKeyShare:
263			// RFC 8446, Section 4.2.8
264			var clientShares cryptobyte.String
265			if !extData.ReadUint16LengthPrefixed(&clientShares) {
266				return nil, errors.New("unable to read key share extension data")
267			}
268			keyShares := []KeyShare{}
269			for !clientShares.Empty() {
270				var ks KeyShare
271				var group uint16
272				if !clientShares.ReadUint16(&group) ||
273					!readUint16LengthPrefixed(&clientShares, &ks.Data) ||
274					len(ks.Data) == 0 {
275					return nil, errors.New("unable to read key share extension data")
276				}
277				ks.Group = CurveID(unGREASEUint16(group))
278				// if not GREASE, key share data will be discarded as it should
279				// be generated per connection
280				if ks.Group != GREASE_PLACEHOLDER {
281					ks.Data = nil
282				}
283				keyShares = append(keyShares, ks)
284			}
285			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &KeyShareExtension{keyShares})
286
287		case extensionPSKModes:
288			// RFC 8446, Section 4.2.9
289			// TODO: PSK Modes have their own form of GREASE-ing which is not currently implemented
290			// the current functionality will NOT re-GREASE/re-randomize these values when using a fingerprinted spec
291			// https://github.com/refraction-networking/utls/pull/58#discussion_r522354105
292			// https://tools.ietf.org/html/draft-ietf-tls-grease-01#section-2
293			pskModes := []uint8{}
294			if !readUint8LengthPrefixed(&extData, &pskModes) {
295				return nil, errors.New("unable to read PSK extension data")
296			}
297			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &PSKKeyExchangeModesExtension{pskModes})
298
299		case utlsExtensionExtendedMasterSecret:
300			// https://tools.ietf.org/html/rfc7627
301			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsExtendedMasterSecretExtension{})
302
303		case utlsExtensionPadding:
304			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle})
305
306		case fakeExtensionChannelID, extensionCompressCertificate, fakeRecordSizeLimit:
307			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})
308
309		case extensionPreSharedKey:
310			// RFC 8446, Section 4.2.11
311			if f.KeepPSK {
312				clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})
313			} else {
314				return nil, errors.New("unsupported extension PreSharedKey")
315			}
316
317		case extensionCookie:
318			// RFC 8446, Section 4.2.2
319			if f.AllowBluntMimicry {
320				clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})
321			} else {
322				return nil, errors.New("unsupported extension Cookie")
323			}
324
325		case extensionEarlyData:
326			// RFC 8446, Section 4.2.10
327			if f.AllowBluntMimicry {
328				clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})
329			} else {
330				return nil, errors.New("unsupported extension EarlyData")
331			}
332
333		default:
334			if isGREASEUint16(extension) {
335				clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsGREASEExtension{unGREASEUint16(extension), extData})
336			} else if f.AllowBluntMimicry {
337				clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})
338			} else {
339				return nil, fmt.Errorf("unsupported extension %#x", extension)
340			}
341
342			continue
343		}
344	}
345
346	if f.AlwaysAddPadding {
347		alreadyHasPadding := false
348		for _, ext := range clientHelloSpec.Extensions {
349			if _, ok := ext.(*UtlsPaddingExtension); ok {
350				alreadyHasPadding = true
351				break
352			}
353		}
354		if !alreadyHasPadding {
355			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle})
356		}
357	}
358
359	return clientHelloSpec, nil
360}
361