mirror of
https://github.com/refraction-networking/utls.git
synced 2025-04-02 03:27:35 +03:00
360 lines
13 KiB
Go
360 lines
13 KiB
Go
// 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 fakeExtensionChannelID, fakeCertCompressionAlgs, 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
|
|
}
|