| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 |
- // 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) || !s.Empty() {
- 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, fakeRecordSizeLimit:
- clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})
- 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 %#x", 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
- }
|