diff --git a/common.go b/common.go index ab8787fc..5d213a16 100644 --- a/common.go +++ b/common.go @@ -150,7 +150,8 @@ const ( // TLS CertificateStatusType (RFC 3546) const ( - statusTypeOCSP uint8 = 1 + statusTypeOCSP uint8 = 1 + statusV2TypeOCSP uint8 = 2 ) // Certificate types (for certificateRequestMsg) diff --git a/handshake_messages.go b/handshake_messages.go index bb0f88d9..296512ea 100644 --- a/handshake_messages.go +++ b/handshake_messages.go @@ -305,7 +305,7 @@ func (m *clientHelloMsg) marshal() ([]byte, error) { } // marshalWithoutBinders returns the ClientHello through the -// PreSharedKeyExtension.identities field, according to RFC 8446, Section +// FakePreSharedKeyExtension.identities field, according to RFC 8446, Section // 4.2.11.2. Note that m.pskBinders must be set to slices of the correct length. func (m *clientHelloMsg) marshalWithoutBinders() ([]byte, error) { bindersLen := 2 // uint16 length prefix diff --git a/internal/helper/typeconv.go b/internal/helper/typeconv.go new file mode 100644 index 00000000..73ec0583 --- /dev/null +++ b/internal/helper/typeconv.go @@ -0,0 +1,23 @@ +package helper + +import ( + "errors" + + "golang.org/x/crypto/cryptobyte" +) + +// Uint8to16 converts a slice of uint8 to a slice of uint16. +// e.g. []uint8{0x00, 0x01, 0x00, 0x02} -> []uint16{0x0001, 0x0002} +func Uint8to16(in []uint8) ([]uint16, error) { + s := cryptobyte.String(in) + var out []uint16 + for !s.Empty() { + var v uint16 + if s.ReadUint16(&v) { + out = append(out, v) + } else { + return nil, errors.New("ReadUint16 failed") + } + } + return out, nil +} diff --git a/u_common.go b/u_common.go index 3fd1f69c..87329f56 100644 --- a/u_common.go +++ b/u_common.go @@ -7,8 +7,14 @@ package tls import ( "crypto/hmac" "crypto/sha512" + "encoding/json" + "errors" "fmt" "hash" + "log" + + "github.com/refraction-networking/utls/internal/helper" + "golang.org/x/crypto/cryptobyte" ) // Naming convention: @@ -35,10 +41,12 @@ const ( utlsFakeExtensionCustom uint16 = 1234 // not IANA assigned, for ALPS // extensions with 'fake' prefix break connection, if server echoes them back + fakeExtensionEncryptThenMAC uint16 = 22 fakeExtensionTokenBinding uint16 = 24 + fakeExtensionDelegatedCredentials uint16 = 34 + fakeExtensionPreSharedKey uint16 = 41 fakeOldExtensionChannelID uint16 = 30031 // not IANA assigned fakeExtensionChannelID uint16 = 30032 // not IANA assigned - fakeExtensionDelegatedCredentials uint16 = 34 ) const ( @@ -161,6 +169,268 @@ type ClientHelloSpec struct { // TLSFingerprintLink string // ?? link to tlsfingerprint.io for informational purposes } +// ReadCipherSuites is a helper function to construct a list of cipher suites from +// a []byte into []uint16. +// +// example: []byte{0x13, 0x01, 0x13, 0x02, 0x13, 0x03} => []uint16{0x1301, 0x1302, 0x1303} +func (chs *ClientHelloSpec) ReadCipherSuites(b []byte) error { + cipherSuites := []uint16{} + s := cryptobyte.String(b) + for !s.Empty() { + var suite uint16 + if !s.ReadUint16(&suite) { + return errors.New("unable to read ciphersuite") + } + cipherSuites = append(cipherSuites, unGREASEUint16(suite)) + } + chs.CipherSuites = cipherSuites + return nil +} + +// ReadCompressionMethods is a helper function to construct a list of compression +// methods from a []byte into []uint8. +func (chs *ClientHelloSpec) ReadCompressionMethods(compressionMethods []byte) error { + chs.CompressionMethods = compressionMethods + return nil +} + +// ReadTLSExtensions is a helper function to construct a list of TLS extensions from +// a byte slice into []TLSExtension. +// +// If keepPSK is not set, the PSK extension will cause an error. +func (chs *ClientHelloSpec) ReadTLSExtensions(b []byte, allowBluntMimicry bool) error { + extensions := cryptobyte.String(b) + for !extensions.Empty() { + var extension uint16 + var extData cryptobyte.String + if !extensions.ReadUint16(&extension) { + return fmt.Errorf("unable to read extension ID") + } + if !extensions.ReadUint16LengthPrefixed(&extData) { + return fmt.Errorf("unable to read data for extension %x", extension) + } + + extWriter := ExtensionIDToExtension(extension) + if extWriter != nil { + if extension == extensionSupportedVersions { + chs.TLSVersMin = 0 + chs.TLSVersMax = 0 + } + if _, err := extWriter.Write(extData); err != nil { + return err + } + + chs.Extensions = append(chs.Extensions, extWriter) + } else { + if allowBluntMimicry { + chs.Extensions = append(chs.Extensions, &GenericExtension{extension, extData}) + } else { + return fmt.Errorf("unsupported extension %d", extension) + } + } + } + return nil +} + +func (chs *ClientHelloSpec) AlwaysAddPadding() { + alreadyHasPadding := false + for _, ext := range chs.Extensions { + if _, ok := ext.(*UtlsPaddingExtension); ok { + alreadyHasPadding = true + break + } + if _, ok := ext.(*FakePreSharedKeyExtension); ok { + alreadyHasPadding = true // PSK must be last, so we don't need to add padding + break + } + } + if !alreadyHasPadding { + chs.Extensions = append(chs.Extensions, &UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle}) + } +} + +// Import TLS ClientHello data from client.tlsfingerprint.io:8443 +// +// data is a map of []byte with following keys: +// - cipher_suites: [10, 10, 19, 1, 19, 2, 19, 3, 192, 43, 192, 47, 192, 44, 192, 48, 204, 169, 204, 168, 192, 19, 192, 20, 0, 156, 0, 157, 0, 47, 0, 53] +// - compression_methods: [0] => null +// - extensions: [10, 10, 255, 1, 0, 45, 0, 35, 0, 16, 68, 105, 0, 11, 0, 43, 0, 18, 0, 13, 0, 0, 0, 10, 0, 27, 0, 5, 0, 51, 0, 23, 10, 10, 0, 21] +// - pt_fmts (ec_point_formats): [1, 0] => len: 1, content: 0x00 +// - sig_algs (signature_algorithms): [0, 16, 4, 3, 8, 4, 4, 1, 5, 3, 8, 5, 5, 1, 8, 6, 6, 1] => len: 16, content: 0x0403, 0x0804, 0x0401, 0x0503, 0x0805, 0x0501, 0x0806, 0x0601 +// - supported_versions: [10, 10, 3, 4, 3, 3] => 0x0a0a, 0x0304, 0x0303 (GREASE, TLS 1.3, TLS 1.2) +// - curves (named_groups, supported_groups): [0, 8, 10, 10, 0, 29, 0, 23, 0, 24] => len: 8, content: GREASE, 0x001d, 0x0017, 0x0018 +// - alpn: [0, 12, 2, 104, 50, 8, 104, 116, 116, 112, 47, 49, 46, 49] => len: 12, content: h2, http/1.1 +// - key_share: [10, 10, 0, 1, 0, 29, 0, 32] => {group: 0x0a0a, len:1}, {group: 0x001d, len:32} +// - psk_key_exchange_modes: [1] => psk_dhe_ke(0x01) +// - cert_compression_algs: [2, 0, 2] => brotli (0x0002) +// - record_size_limit: [0, 255] => 255 +// +// TLSVersMin/TLSVersMax are set to 0 if supported_versions is present. +// To prevent conflict, they should be set manually if needed BEFORE calling this function. +func (chs *ClientHelloSpec) ImportTLSClientHello(data map[string][]byte) error { + var tlsExtensionTypes []uint16 + var err error + + if data["cipher_suites"] == nil { + return errors.New("cipher_suites is required") + } + chs.CipherSuites, err = helper.Uint8to16(data["cipher_suites"]) + if err != nil { + return err + } + + if data["compression_methods"] == nil { + return errors.New("compression_methods is required") + } + chs.CompressionMethods = data["compression_methods"] + + if data["extensions"] == nil { + return errors.New("extensions is required") + } + tlsExtensionTypes, err = helper.Uint8to16(data["extensions"]) + if err != nil { + return err + } + + for _, extType := range tlsExtensionTypes { + extension := ExtensionIDToExtension(extType) + if extension == nil { + log.Printf("[Warning] Unsupported extension %d added as a &GenericExtension without Data", extType) + chs.Extensions = append(chs.Extensions, &GenericExtension{extType, []byte{}}) + } else { + switch extType { + case extensionSupportedPoints: + if data["pt_fmts"] == nil { + return errors.New("pt_fmts is required") + } + _, err = extension.Write(data["pt_fmts"]) + if err != nil { + return err + } + case extensionSignatureAlgorithms: + if data["sig_algs"] == nil { + return errors.New("sig_algs is required") + } + _, err = extension.Write(data["sig_algs"]) + if err != nil { + return err + } + case extensionSupportedVersions: + chs.TLSVersMin = 0 + chs.TLSVersMax = 0 + + if data["supported_versions"] == nil { + return errors.New("supported_versions is required") + } + + // need to add uint8 length prefix + fixedData := make([]byte, len(data["supported_versions"])+1) + fixedData[0] = uint8(len(data["supported_versions"]) & 0xff) + copy(fixedData[1:], data["supported_versions"]) + _, err = extension.Write(fixedData) + if err != nil { + return err + } + case extensionSupportedCurves: + if data["curves"] == nil { + return errors.New("curves is required") + } + + _, err = extension.Write(data["curves"]) + if err != nil { + return err + } + case extensionALPN: + if data["alpn"] == nil { + return errors.New("alpn is required") + } + + _, err = extension.Write(data["alpn"]) + if err != nil { + return err + } + case extensionKeyShare: + if data["key_share"] == nil { + return errors.New("key_share is required") + } + + // need to add (zero) data per each key share, [10, 10, 0, 1] => [10, 10, 0, 1, 0] + fixedData := make([]byte, 0) + for i := 0; i < len(data["key_share"]); i += 4 { + fixedData = append(fixedData, data["key_share"][i:i+4]...) + for j := 0; j < int(data["key_share"][i+3]); j++ { + fixedData = append(fixedData, 0) + } + } + // add uint16 length prefix + fixedData = append([]byte{uint8(len(fixedData) >> 8), uint8(len(fixedData) & 0xff)}, fixedData...) + + _, err = extension.Write(fixedData) + if err != nil { + return err + } + case extensionPSKModes: + if data["psk_key_exchange_modes"] == nil { + return errors.New("psk_key_exchange_modes is required") + } + + // need to add uint8 length prefix + fixedData := make([]byte, len(data["psk_key_exchange_modes"])+1) + fixedData[0] = uint8(len(data["psk_key_exchange_modes"]) & 0xff) + copy(fixedData[1:], data["psk_key_exchange_modes"]) + _, err = extension.Write(fixedData) + if err != nil { + return err + } + case utlsExtensionCompressCertificate: + if data["cert_compression_algs"] == nil { + return errors.New("cert_compression_algs is required") + } + + // need to add uint8 length prefix + fixedData := make([]byte, len(data["cert_compression_algs"])+1) + fixedData[0] = uint8(len(data["cert_compression_algs"]) & 0xff) + copy(fixedData[1:], data["cert_compression_algs"]) + _, err = extension.Write(fixedData) + if err != nil { + return err + } + case fakeRecordSizeLimit: + if data["record_size_limit"] == nil { + return errors.New("record_size_limit is required") + } + + _, err = extension.Write(data["record_size_limit"]) // uint16 as []byte + if err != nil { + return err + } + case utlsExtensionApplicationSettings: + // TODO: tlsfingerprint.io should record/provide application settings data + extension.(*ApplicationSettingsExtension).SupportedProtocols = []string{"h2"} + case fakeExtensionPreSharedKey: + log.Printf("[Warning] PSK extension added without data") + default: + if !isGREASEUint16(extType) { + log.Printf("[Warning] extension %d added without data", extType) + } /*else { + log.Printf("[Warning] GREASE extension added but ID/Data discarded. They will be automatically re-GREASEd on ApplyPreset() call.") + }*/ + } + chs.Extensions = append(chs.Extensions, extension) + } + } + return nil +} + +func (chs *ClientHelloSpec) ImportTLSClientHelloFromJSON(jsonB []byte) error { + var data map[string][]byte + err := json.Unmarshal(jsonB, &data) + if err != nil { + return err + } + return chs.ImportTLSClientHello(data) +} + var ( // HelloGolang will use default "crypto/tls" handshake marshaling codepath, which WILL // overwrite your changes to Hello(Config, Session are fine). diff --git a/u_conn.go b/u_conn.go index d468f093..caff710d 100644 --- a/u_conn.go +++ b/u_conn.go @@ -599,7 +599,7 @@ func (uconn *UConn) SetTLSVers(minTLSVers, maxTLSVers uint16, specExtensions []T minVers := uint16(0) maxVers := uint16(0) for _, vers := range versions { - if vers == GREASE_PLACEHOLDER { + if isGREASEUint16(vers) { continue } if maxVers < vers || maxVers == 0 { diff --git a/u_fingerprinter.go b/u_fingerprinter.go index f4d49987..478f222b 100644 --- a/u_fingerprinter.go +++ b/u_fingerprinter.go @@ -6,16 +6,12 @@ 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 @@ -40,8 +36,8 @@ type Fingerprinter struct { // 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{} +func (f *Fingerprinter) FingerprintClientHello(data []byte) (clientHelloSpec *ClientHelloSpec, err error) { + clientHelloSpec = &ClientHelloSpec{} s := cryptobyte.String(data) var contentType uint8 @@ -75,23 +71,25 @@ func (f *Fingerprinter) FingerprintClientHello(data []byte) (*ClientHelloSpec, e return nil, errors.New("unable to read session id") } + // CipherSuites 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)) + err = clientHelloSpec.ReadCipherSuites(cipherSuitesBytes) + if err != nil { + return nil, err } - clientHelloSpec.CipherSuites = cipherSuites - if !readUint8LengthPrefixed(&s, &clientHelloSpec.CompressionMethods) { + // CompressionMethods + var compressionMethods cryptobyte.String + if !s.ReadUint8LengthPrefixed(&compressionMethods) { return nil, errors.New("unable to read compression methods") } + err = clientHelloSpec.ReadCompressionMethods(compressionMethods) + if err != nil { + return nil, err + } if s.Empty() { // ClientHello is optionally followed by extension data @@ -103,327 +101,13 @@ func (f *Fingerprinter) FingerprintClientHello(data []byte) (*ClientHelloSpec, e 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 - } + err = clientHelloSpec.ReadTLSExtensions(extensions, f.AllowBluntMimicry) + if err != nil { + return nil, err } 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}) - } + clientHelloSpec.AlwaysAddPadding() } return clientHelloSpec, nil diff --git a/u_fingerprinter_test.go b/u_fingerprinter_test.go index 0f761aad..ad140bf1 100644 --- a/u_fingerprinter_test.go +++ b/u_fingerprinter_test.go @@ -501,12 +501,6 @@ func TestUTLSFingerprintClientHelloKeepPSK(t *testing.T) { } f := &Fingerprinter{} - _, err = f.FingerprintClientHello(helloBytes) - if err == nil { - t.Errorf("expected error generating spec from client hello with PSK") - } - - f = &Fingerprinter{KeepPSK: true} generatedSpec, err := f.FingerprintClientHello(helloBytes) if err != nil { t.Errorf("got error: %v; expected to succeed", err) @@ -514,10 +508,8 @@ func TestUTLSFingerprintClientHelloKeepPSK(t *testing.T) { } for _, ext := range generatedSpec.Extensions { - if genericExtension, ok := (ext).(*GenericExtension); ok { - if genericExtension.Id == extensionPreSharedKey { - return - } + if _, ok := (ext).(*FakePreSharedKeyExtension); ok { + return } } t.Errorf("generated ClientHelloSpec with KeepPSK does not include preshared key extension") diff --git a/u_parrots.go b/u_parrots.go index d141c9e6..73f5e4dc 100644 --- a/u_parrots.go +++ b/u_parrots.go @@ -1988,7 +1988,7 @@ func (uconn *UConn) ApplyPreset(p *ClientHelloSpec) error { hello.CipherSuites = make([]uint16, len(p.CipherSuites)) copy(hello.CipherSuites, p.CipherSuites) for i := range hello.CipherSuites { - if hello.CipherSuites[i] == GREASE_PLACEHOLDER { + if isGREASEUint16(hello.CipherSuites[i]) { // just in case the user set a GREASE value instead of unGREASEd hello.CipherSuites[i] = GetBoringGREASEValue(uconn.greaseSeed, ssl_grease_cipher) } } @@ -2029,7 +2029,7 @@ func (uconn *UConn) ApplyPreset(p *ClientHelloSpec) error { } case *SupportedCurvesExtension: for i := range ext.Curves { - if ext.Curves[i] == GREASE_PLACEHOLDER { + if isGREASEUint16(uint16(ext.Curves[i])) { ext.Curves[i] = CurveID(GetBoringGREASEValue(uconn.greaseSeed, ssl_grease_group)) } } @@ -2037,7 +2037,7 @@ func (uconn *UConn) ApplyPreset(p *ClientHelloSpec) error { preferredCurveIsSet := false for i := range ext.KeyShares { curveID := ext.KeyShares[i].Group - if curveID == GREASE_PLACEHOLDER { + if isGREASEUint16(uint16(curveID)) { // just in case the user set a GREASE value instead of unGREASEd ext.KeyShares[i].Group = CurveID(GetBoringGREASEValue(uconn.greaseSeed, ssl_grease_group)) continue } @@ -2059,7 +2059,7 @@ func (uconn *UConn) ApplyPreset(p *ClientHelloSpec) error { } case *SupportedVersionsExtension: for i := range ext.Versions { - if ext.Versions[i] == GREASE_PLACEHOLDER { + if isGREASEUint16(ext.Versions[i]) { // just in case the user set a GREASE value instead of unGREASEd ext.Versions[i] = GetBoringGREASEValue(uconn.greaseSeed, ssl_grease_version) } } diff --git a/u_public.go b/u_public.go index 164d15e2..e128d538 100644 --- a/u_public.go +++ b/u_public.go @@ -344,7 +344,7 @@ type PubClientHelloMsg struct { KeyShares []KeyShare EarlyData bool PskModes []uint8 - PskIdentities []pskIdentity + PskIdentities []PskIdentity PskBinders [][]byte } @@ -379,7 +379,7 @@ func (chm *PubClientHelloMsg) getPrivatePtr() *clientHelloMsg { keyShares: KeyShares(chm.KeyShares).ToPrivate(), earlyData: chm.EarlyData, pskModes: chm.PskModes, - pskIdentities: chm.PskIdentities, + pskIdentities: PskIdentities(chm.PskIdentities).ToPrivate(), pskBinders: chm.PskBinders, } } @@ -416,7 +416,7 @@ func (chm *clientHelloMsg) getPublicPtr() *PubClientHelloMsg { KeyShares: keyShares(chm.keyShares).ToPublic(), EarlyData: chm.earlyData, PskModes: chm.pskModes, - PskIdentities: chm.pskIdentities, + PskIdentities: pskIdentities(chm.pskIdentities).ToPublic(), PskBinders: chm.pskBinders, } } @@ -562,6 +562,32 @@ func (KSS KeyShares) ToPrivate() []keyShare { return kss } +// TLS 1.3 PSK Identity. Can be a Session Ticket, or a reference to a saved +// session. See RFC 8446, Section 4.2.11. +type PskIdentity struct { + Label []byte + ObfuscatedTicketAge uint32 +} + +type PskIdentities []PskIdentity +type pskIdentities []pskIdentity + +func (pss pskIdentities) ToPublic() []PskIdentity { + var PSS []PskIdentity + for _, ps := range pss { + PSS = append(PSS, PskIdentity{Label: ps.label, ObfuscatedTicketAge: ps.obfuscatedTicketAge}) + } + return PSS +} + +func (PSS PskIdentities) ToPrivate() []pskIdentity { + var pss []pskIdentity + for _, PS := range PSS { + pss = append(pss, pskIdentity{label: PS.Label, obfuscatedTicketAge: PS.ObfuscatedTicketAge}) + } + return pss +} + // ClientSessionState is public, but all its fields are private. Let's add setters, getters and constructor // ClientSessionState contains the state needed by clients to resume TLS sessions. diff --git a/u_tls_extensions.go b/u_tls_extensions.go index f386b86f..242fddec 100644 --- a/u_tls_extensions.go +++ b/u_tls_extensions.go @@ -7,8 +7,79 @@ package tls import ( "errors" "io" + "strings" + + "golang.org/x/crypto/cryptobyte" ) +// ExtensionIDToExtension returns a TLSExtension for the given extension ID. +func ExtensionIDToExtension(id uint16) TLSExtensionWriter { + // deep copy + switch id { + case extensionServerName: + return &SNIExtension{} + case extensionStatusRequest: + return &StatusRequestExtension{} + case extensionSupportedCurves: + return &SupportedCurvesExtension{} + case extensionSupportedPoints: + return &SupportedPointsExtension{} + case extensionSignatureAlgorithms: + return &SignatureAlgorithmsExtension{} + case extensionALPN: + return &ALPNExtension{} + case extensionStatusRequestV2: + return &StatusRequestV2Extension{} + case extensionSCT: + return &SCTExtension{} + case utlsExtensionPadding: + return &UtlsPaddingExtension{} + case utlsExtensionExtendedMasterSecret: + return &UtlsExtendedMasterSecretExtension{} + case fakeExtensionTokenBinding: + return &FakeTokenBindingExtension{} + case utlsExtensionCompressCertificate: + return &UtlsCompressCertExtension{} + case fakeExtensionDelegatedCredentials: + return &FakeDelegatedCredentialsExtension{} + case extensionSessionTicket: + return &SessionTicketExtension{} + case fakeExtensionPreSharedKey: + return &FakePreSharedKeyExtension{} + // case extensionEarlyData: + // return &EarlyDataExtension{} + case extensionSupportedVersions: + return &SupportedVersionsExtension{} + // case extensionCookie: + // return &CookieExtension{} + case extensionPSKModes: + return &PSKKeyExchangeModesExtension{} + // case extensionCertificateAuthorities: + // return &CertificateAuthoritiesExtension{} + case extensionSignatureAlgorithmsCert: + return &SignatureAlgorithmsCertExtension{} + case extensionKeyShare: + return &KeyShareExtension{} + case extensionNextProtoNeg: + return &NPNExtension{} + case utlsExtensionApplicationSettings: + return &ApplicationSettingsExtension{} + case fakeOldExtensionChannelID: + return &FakeChannelIDExtension{true} + case fakeExtensionChannelID: + return &FakeChannelIDExtension{} + case fakeRecordSizeLimit: + return &FakeRecordSizeLimitExtension{} + case extensionRenegotiationInfo: + return &RenegotiationInfoExtension{} + default: + if isGREASEUint16(id) { + return &UtlsGREASEExtension{} + } + return nil // not returning GenericExtension, it should be handled by caller + } +} + type TLSExtension interface { writeToUConn(*UConn) error @@ -19,6 +90,16 @@ type TLSExtension interface { Read(p []byte) (n int, err error) // implements io.Reader } +// TLSExtensionWriter is an interface allowing a TLS extension to be +// auto-constucted/recovered by reading in a byte stream. +type TLSExtensionWriter interface { + TLSExtension + + // Write writes up to len(b) bytes from b. + // It returns the number of bytes written (0 <= n <= len(b)) and any error encountered. + Write(b []byte) (n int, err error) +} + type NPNExtension struct { NextProtos []string } @@ -43,6 +124,12 @@ func (e *NPNExtension) Read(b []byte) (int, error) { return e.Len(), io.EOF } +// Write is a no-op for NPNExtension. NextProtos are not included in the +// ClientHello. +func (e *NPNExtension) Write(_ []byte) (int, error) { + return 0, nil +} + type SNIExtension struct { ServerName string // not an array because go crypto/tls doesn't support multiple SNIs } @@ -89,6 +176,42 @@ func (e *SNIExtension) Read(b []byte) (int, error) { return e.Len(), io.EOF } +// Write is a no-op for StatusRequestExtension. +// SNI should not be fingerprinted and is user controlled. +func (e *SNIExtension) Write(b []byte) (int, error) { + fullLen := len(b) + extData := cryptobyte.String(b) + // RFC 6066, Section 3 + var nameList cryptobyte.String + if !extData.ReadUint16LengthPrefixed(&nameList) || nameList.Empty() { + return fullLen, 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 fullLen, errors.New("unable to read server name extension data") + } + if nameType != 0 { + continue + } + if len(serverName) != 0 { + return fullLen, errors.New("multiple names of the same name_type in server name extension are prohibited") + } + serverName = string(serverNameBytes) + if strings.HasSuffix(serverName, ".") { + return fullLen, errors.New("SNI value may not include a trailing dot") + } + } + // clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SNIExtension{}) // gaukas moved this line out from the loop. + + // don't copy SNI from ClientHello to ClientHelloSpec! + return fullLen, nil +} + type StatusRequestExtension struct { } @@ -115,6 +238,26 @@ func (e *StatusRequestExtension) Read(b []byte) (int, error) { return e.Len(), io.EOF } +// Write is a no-op for StatusRequestExtension. No data for this extension. +func (e *StatusRequestExtension) Write(b []byte) (int, error) { + fullLen := len(b) + extData := cryptobyte.String(b) + // RFC 4366, Section 3.6 + var statusType uint8 + var ignored cryptobyte.String + if !extData.ReadUint8(&statusType) || + !extData.ReadUint16LengthPrefixed(&ignored) || + !extData.ReadUint16LengthPrefixed(&ignored) { + return fullLen, errors.New("unable to read status request extension data") + } + + if statusType != statusTypeOCSP { + return fullLen, errors.New("status request extension statusType is not statusTypeOCSP(1)") + } + + return fullLen, nil +} + type StatusRequestV2Extension struct { } @@ -145,6 +288,28 @@ func (e *StatusRequestV2Extension) Read(b []byte) (int, error) { return e.Len(), io.EOF } +// Write is a no-op for StatusRequestV2Extension. No data for this extension. +func (e *StatusRequestV2Extension) Write(b []byte) (int, error) { + fullLen := len(b) + extData := cryptobyte.String(b) + // RFC 4366, Section 3.6 + var statusType uint8 + var ignored cryptobyte.String + if !extData.ReadUint16LengthPrefixed(&ignored) || + !extData.ReadUint8(&statusType) || + !extData.ReadUint16LengthPrefixed(&ignored) || + !extData.ReadUint16LengthPrefixed(&ignored) || + !extData.ReadUint16LengthPrefixed(&ignored) { + return fullLen, errors.New("unable to read status request v2 extension data") + } + + if statusType != statusV2TypeOCSP { + return fullLen, errors.New("status request v2 extension statusType is not statusV2TypeOCSP(2)") + } + + return fullLen, nil +} + type SupportedCurvesExtension struct { Curves []CurveID } @@ -177,6 +342,26 @@ func (e *SupportedCurvesExtension) Read(b []byte) (int, error) { return e.Len(), io.EOF } +func (e *SupportedCurvesExtension) Write(b []byte) (int, error) { + fullLen := len(b) + extData := cryptobyte.String(b) + // RFC 4492, sections 5.1.1 and RFC 8446, Section 4.2.7 + var curvesBytes cryptobyte.String + if !extData.ReadUint16LengthPrefixed(&curvesBytes) || curvesBytes.Empty() { + return 0, errors.New("unable to read supported curves extension data") + } + curves := []CurveID{} + for !curvesBytes.Empty() { + var curve uint16 + if !curvesBytes.ReadUint16(&curve) { + return 0, errors.New("unable to read supported curves extension data") + } + curves = append(curves, CurveID(unGREASEUint16(curve))) + } + e.Curves = curves + return fullLen, nil +} + type SupportedPointsExtension struct { SupportedPoints []uint8 } @@ -206,6 +391,19 @@ func (e *SupportedPointsExtension) Read(b []byte) (int, error) { return e.Len(), io.EOF } +func (e *SupportedPointsExtension) Write(b []byte) (int, error) { + fullLen := len(b) + extData := cryptobyte.String(b) + // RFC 4492, Section 5.1.2 + supportedPoints := []uint8{} + if !readUint8LengthPrefixed(&extData, &supportedPoints) || + len(supportedPoints) == 0 { + return 0, errors.New("unable to read supported points extension data") + } + e.SupportedPoints = supportedPoints + return fullLen, nil +} + type SignatureAlgorithmsExtension struct { SupportedSignatureAlgorithms []SignatureScheme } @@ -237,6 +435,27 @@ func (e *SignatureAlgorithmsExtension) Read(b []byte) (int, error) { return e.Len(), io.EOF } +func (e *SignatureAlgorithmsExtension) Write(b []byte) (int, error) { + fullLen := len(b) + extData := cryptobyte.String(b) + // RFC 5246, Section 7.4.1.4.1 + var sigAndAlgs cryptobyte.String + if !extData.ReadUint16LengthPrefixed(&sigAndAlgs) || sigAndAlgs.Empty() { + return 0, errors.New("unable to read signature algorithms extension data") + } + supportedSignatureAlgorithms := []SignatureScheme{} + for !sigAndAlgs.Empty() { + var sigAndAlg uint16 + if !sigAndAlgs.ReadUint16(&sigAndAlg) { + return 0, errors.New("unable to read signature algorithms extension data") + } + supportedSignatureAlgorithms = append( + supportedSignatureAlgorithms, SignatureScheme(sigAndAlg)) + } + e.SupportedSignatureAlgorithms = supportedSignatureAlgorithms + return fullLen, nil +} + type SignatureAlgorithmsCertExtension struct { SupportedSignatureAlgorithms []SignatureScheme } @@ -268,6 +487,30 @@ func (e *SignatureAlgorithmsCertExtension) Read(b []byte) (int, error) { return e.Len(), io.EOF } +// Write implementation copied from SignatureAlgorithmsExtension.Write +// +// Warning: not tested. +func (e *SignatureAlgorithmsCertExtension) Write(b []byte) (int, error) { + fullLen := len(b) + extData := cryptobyte.String(b) + // RFC 8446, Section 4.2.3 + var sigAndAlgs cryptobyte.String + if !extData.ReadUint16LengthPrefixed(&sigAndAlgs) || sigAndAlgs.Empty() { + return 0, errors.New("unable to read signature algorithms extension data") + } + supportedSignatureAlgorithms := []SignatureScheme{} + for !sigAndAlgs.Empty() { + var sigAndAlg uint16 + if !sigAndAlgs.ReadUint16(&sigAndAlg) { + return 0, errors.New("unable to read signature algorithms extension data") + } + supportedSignatureAlgorithms = append( + supportedSignatureAlgorithms, SignatureScheme(sigAndAlg)) + } + e.SupportedSignatureAlgorithms = supportedSignatureAlgorithms + return fullLen, nil +} + type RenegotiationInfoExtension struct { // Renegotiation field limits how many times client will perform renegotiation: no limit, once, or never. // The extension still will be sent, even if Renegotiation is set to RenegotiateNever. @@ -310,6 +553,11 @@ func (e *RenegotiationInfoExtension) Read(b []byte) (int, error) { return e.Len(), io.EOF } +func (e *RenegotiationInfoExtension) Write(_ []byte) (int, error) { + e.Renegotiation = RenegotiateOnceAsClient + return 0, nil +} + type ALPNExtension struct { AlpnProtocols []string } @@ -356,6 +604,27 @@ func (e *ALPNExtension) Read(b []byte) (int, error) { return e.Len(), io.EOF } +func (e *ALPNExtension) Write(b []byte) (int, error) { + fullLen := len(b) + extData := cryptobyte.String(b) + // RFC 7301, Section 3.1 + var protoList cryptobyte.String + if !extData.ReadUint16LengthPrefixed(&protoList) || protoList.Empty() { + return 0, errors.New("unable to read ALPN extension data") + } + alpnProtocols := []string{} + for !protoList.Empty() { + var proto cryptobyte.String + if !protoList.ReadUint8LengthPrefixed(&proto) || proto.Empty() { + return 0, errors.New("unable to read ALPN extension data") + } + alpnProtocols = append(alpnProtocols, string(proto)) + + } + e.AlpnProtocols = alpnProtocols + return fullLen, nil +} + // ApplicationSettingsExtension represents the TLS ALPS extension. // At the time of this writing, this extension is currently a draft: // https://datatracker.ietf.org/doc/html/draft-vvv-tls-alps-01 @@ -405,6 +674,28 @@ func (e *ApplicationSettingsExtension) Read(b []byte) (int, error) { return e.Len(), io.EOF } +// Write implementation copied from ALPNExtension.Write +func (e *ApplicationSettingsExtension) Write(b []byte) (int, error) { + fullLen := len(b) + extData := cryptobyte.String(b) + // https://datatracker.ietf.org/doc/html/draft-vvv-tls-alps-01 + var protoList cryptobyte.String + if !extData.ReadUint16LengthPrefixed(&protoList) || protoList.Empty() { + return 0, errors.New("unable to read ALPN extension data") + } + alpnProtocols := []string{} + for !protoList.Empty() { + var proto cryptobyte.String + if !protoList.ReadUint8LengthPrefixed(&proto) || proto.Empty() { + return 0, errors.New("unable to read ALPN extension data") + } + alpnProtocols = append(alpnProtocols, string(proto)) + + } + e.SupportedProtocols = alpnProtocols + return fullLen, nil +} + type SCTExtension struct { } @@ -428,6 +719,10 @@ func (e *SCTExtension) Read(b []byte) (int, error) { return e.Len(), io.EOF } +func (e *SCTExtension) Write(_ []byte) (int, error) { + return 0, nil +} + type SessionTicketExtension struct { Session *ClientSessionState } @@ -464,6 +759,11 @@ func (e *SessionTicketExtension) Read(b []byte) (int, error) { return e.Len(), io.EOF } +func (e *SessionTicketExtension) Write(_ []byte) (int, error) { + // RFC 5077, Section 3.2 + return 0, nil +} + // GenericExtension allows to include in ClientHello arbitrary unsupported extensions. type GenericExtension struct { Id uint16 @@ -518,6 +818,11 @@ func (e *UtlsExtendedMasterSecretExtension) Read(b []byte) (int, error) { return e.Len(), io.EOF } +func (e *UtlsExtendedMasterSecretExtension) Write(_ []byte) (int, error) { + // https://tools.ietf.org/html/rfc7627 + return 0, nil +} + var extendedMasterSecretLabel = []byte("extended master secret") // extendedMasterFromPreMasterSecret generates the master secret from the pre-master @@ -580,6 +885,13 @@ func (e *UtlsGREASEExtension) Read(b []byte) (int, error) { return e.Len(), io.EOF } +func (e *UtlsGREASEExtension) Write(b []byte) (int, error) { + e.Value = GREASE_PLACEHOLDER + e.Body = make([]byte, len(b)) + n := copy(e.Body, b) + return n, nil +} + type UtlsPaddingExtension struct { PaddingLen int WillPad bool // set to false to disable extension @@ -622,6 +934,25 @@ func (e *UtlsPaddingExtension) Read(b []byte) (int, error) { return e.Len(), io.EOF } +func (e *UtlsPaddingExtension) Write(_ []byte) (int, error) { + e.GetPaddingLen = BoringPaddingStyle + return 0, nil +} + +// https://github.com/google/boringssl/blob/7d7554b6b3c79e707e25521e61e066ce2b996e4c/ssl/t1_lib.c#L2803 +func BoringPaddingStyle(unpaddedLen int) (int, bool) { + if unpaddedLen > 0xff && unpaddedLen < 0x200 { + paddingLen := 0x200 - unpaddedLen + if paddingLen >= 4+1 { + paddingLen -= 4 + } else { + paddingLen = 1 + } + return paddingLen, true + } + return 0, false +} + // UtlsCompressCertExtension is only implemented client-side, for server certificates. Alternate // certificate message formats (https://datatracker.ietf.org/doc/html/rfc7250) are not supported. // @@ -667,18 +998,24 @@ func (e *UtlsCompressCertExtension) Read(b []byte) (int, error) { return e.Len(), io.EOF } -// https://github.com/google/boringssl/blob/7d7554b6b3c79e707e25521e61e066ce2b996e4c/ssl/t1_lib.c#L2803 -func BoringPaddingStyle(unpaddedLen int) (int, bool) { - if unpaddedLen > 0xff && unpaddedLen < 0x200 { - paddingLen := 0x200 - unpaddedLen - if paddingLen >= 4+1 { - paddingLen -= 4 - } else { - paddingLen = 1 +func (e *UtlsCompressCertExtension) Write(b []byte) (int, error) { + fullLen := len(b) + extData := cryptobyte.String(b) + methods := []CertCompressionAlgo{} + methodsRaw := new(cryptobyte.String) + if !extData.ReadUint8LengthPrefixed(methodsRaw) { + return 0, errors.New("unable to read cert compression algorithms extension data") + } + for !methodsRaw.Empty() { + var method uint16 + if !methodsRaw.ReadUint16(&method) { + return 0, errors.New("unable to read cert compression algorithms extension data") } - return paddingLen, true + methods = append(methods, CertCompressionAlgo(method)) } - return 0, false + + e.Algorithms = methods + return fullLen, nil } /* TLS 1.3 */ @@ -724,6 +1061,35 @@ func (e *KeyShareExtension) Read(b []byte) (int, error) { return e.Len(), io.EOF } +func (e *KeyShareExtension) Write(b []byte) (int, error) { + fullLen := len(b) + extData := cryptobyte.String(b) + // RFC 8446, Section 4.2.8 + var clientShares cryptobyte.String + if !extData.ReadUint16LengthPrefixed(&clientShares) { + return 0, 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 0, 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) + } + e.KeyShares = keyShares + return fullLen, nil +} + func (e *KeyShareExtension) writeToUConn(uc *UConn) error { uc.HandshakeState.Hello.KeyShares = e.KeyShares return nil @@ -761,6 +1127,22 @@ func (e *PSKKeyExchangeModesExtension) Read(b []byte) (int, error) { return e.Len(), io.EOF } +func (e *PSKKeyExchangeModesExtension) Write(b []byte) (int, error) { + fullLen := len(b) + extData := cryptobyte.String(b) + // 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 0, errors.New("unable to read PSK extension data") + } + e.Modes = pskModes + return fullLen, nil +} + func (e *PSKKeyExchangeModesExtension) writeToUConn(uc *UConn) error { uc.HandshakeState.Hello.PskModes = e.Modes return nil @@ -803,6 +1185,26 @@ func (e *SupportedVersionsExtension) Read(b []byte) (int, error) { return e.Len(), io.EOF } +func (e *SupportedVersionsExtension) Write(b []byte) (int, error) { + fullLen := len(b) + extData := cryptobyte.String(b) + // RFC 8446, Section 4.2.1 + var versList cryptobyte.String + if !extData.ReadUint8LengthPrefixed(&versList) || versList.Empty() { + return 0, errors.New("unable to read supported versions extension data") + } + supportedVersions := []uint16{} + for !versList.Empty() { + var vers uint16 + if !versList.ReadUint16(&vers) { + return 0, errors.New("unable to read supported versions extension data") + } + supportedVersions = append(supportedVersions, unGREASEUint16(vers)) + } + e.Versions = supportedVersions + return fullLen, nil +} + // MUST NOT be part of initial ClientHello type CookieExtension struct { Cookie []byte @@ -863,6 +1265,10 @@ func (e *FakeChannelIDExtension) Read(b []byte) (int, error) { return e.Len(), io.EOF } +func (e *FakeChannelIDExtension) Write(_ []byte) (int, error) { + return 0, nil +} + type FakeRecordSizeLimitExtension struct { Limit uint16 } @@ -891,6 +1297,15 @@ func (e *FakeRecordSizeLimitExtension) Read(b []byte) (int, error) { return e.Len(), io.EOF } +func (e *FakeRecordSizeLimitExtension) Write(b []byte) (int, error) { + fullLen := len(b) + extData := cryptobyte.String(b) + if !extData.ReadUint16(&e.Limit) { + return 0, errors.New("unable to read record size limit extension data") + } + return fullLen, nil +} + type DelegatedCredentialsExtension struct { AlgorithmsSignature []SignatureScheme } @@ -921,7 +1336,6 @@ func (e *DelegatedCredentialsExtension) Read(b []byte) (int, error) { } // https://tools.ietf.org/html/rfc8472#section-2 - type FakeTokenBindingExtension struct { MajorVersion, MinorVersion uint8 KeyParameters []uint8 @@ -954,6 +1368,19 @@ func (e *FakeTokenBindingExtension) Read(b []byte) (int, error) { return e.Len(), io.EOF } +func (e *FakeTokenBindingExtension) Write(b []byte) (int, error) { + fullLen := len(b) + extData := cryptobyte.String(b) + var keyParameters cryptobyte.String + if !extData.ReadUint8(&e.MajorVersion) || + !extData.ReadUint8(&e.MinorVersion) || + !extData.ReadUint8LengthPrefixed(&keyParameters) { + return 0, errors.New("unable to read token binding extension data") + } + e.KeyParameters = keyParameters + return fullLen, nil +} + // https://datatracker.ietf.org/doc/html/draft-ietf-tls-subcerts-15#section-4.1.1 type FakeDelegatedCredentialsExtension struct { @@ -985,3 +1412,175 @@ func (e *FakeDelegatedCredentialsExtension) Read(b []byte) (int, error) { } return e.Len(), io.EOF } + +func (e *FakeDelegatedCredentialsExtension) Write(b []byte) (int, error) { + fullLen := len(b) + extData := cryptobyte.String(b) + //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 0, errors.New("unable to read signature algorithms extension data") + } + supportedSignatureAlgorithms := []SignatureScheme{} + for !supportedAlgs.Empty() { + var sigAndAlg uint16 + if !supportedAlgs.ReadUint16(&sigAndAlg) { + return 0, errors.New("unable to read signature algorithms extension data") + } + supportedSignatureAlgorithms = append( + supportedSignatureAlgorithms, SignatureScheme(sigAndAlg)) + } + e.SupportedSignatureAlgorithms = supportedSignatureAlgorithms + return fullLen, nil +} + +// FakePreSharedKeyExtension is an extension used to set the PSK extension in the +// ClientHello. +// +// Unfortunately, even when the PSK extension is set, there will be no PSK-based +// resumption since crypto/tls does not implement PSK. +type FakePreSharedKeyExtension struct { + PskIdentities []PskIdentity + PskBinders [][]byte +} + +func (e *FakePreSharedKeyExtension) writeToUConn(uc *UConn) error { + uc.HandshakeState.Hello.PskIdentities = e.PskIdentities + uc.HandshakeState.Hello.PskBinders = e.PskBinders + return nil +} + +func (e *FakePreSharedKeyExtension) Len() int { + length := 4 // extension type + extension length + length += 2 // identities length + for _, identity := range e.PskIdentities { + length += 2 + len(identity.Label) + 4 // identity length + identity + obfuscated ticket age + } + length += 2 // binders length + for _, binder := range e.PskBinders { + length += len(binder) + } + return length +} + +func (e *FakePreSharedKeyExtension) Read(b []byte) (int, error) { + if len(b) < e.Len() { + return 0, io.ErrShortBuffer + } + + b[0] = byte(extensionPreSharedKey >> 8) + b[1] = byte(extensionPreSharedKey) + b[2] = byte((e.Len() - 4) >> 8) + b[3] = byte(e.Len() - 4) + + // identities length + identitiesLength := 0 + for _, identity := range e.PskIdentities { + identitiesLength += 2 + len(identity.Label) + 4 // identity length + identity + obfuscated ticket age + } + b[4] = byte(identitiesLength >> 8) + b[5] = byte(identitiesLength) + + // identities + offset := 6 + for _, identity := range e.PskIdentities { + b[offset] = byte(len(identity.Label) >> 8) + b[offset+1] = byte(len(identity.Label)) + offset += 2 + copy(b[offset:], identity.Label) + offset += len(identity.Label) + b[offset] = byte(identity.ObfuscatedTicketAge >> 24) + b[offset+1] = byte(identity.ObfuscatedTicketAge >> 16) + b[offset+2] = byte(identity.ObfuscatedTicketAge >> 8) + b[offset+3] = byte(identity.ObfuscatedTicketAge) + offset += 4 + } + + // binders length + bindersLength := 0 + for _, binder := range e.PskBinders { + bindersLength += len(binder) + } + b[offset] = byte(bindersLength >> 8) + b[offset+1] = byte(bindersLength) + offset += 2 + + // binders + for _, binder := range e.PskBinders { + copy(b[offset:], binder) + offset += len(binder) + } + + return e.Len(), io.EOF +} + +func (e *FakePreSharedKeyExtension) Write(b []byte) (n int, err error) { + fullLen := len(b) + s := cryptobyte.String(b) + + var identitiesLength uint16 + if !s.ReadUint16(&identitiesLength) { + return 0, errors.New("tls: invalid PSK extension") + } + + // identities + for identitiesLength > 0 { + var identityLength uint16 + if !s.ReadUint16(&identityLength) { + return 0, errors.New("tls: invalid PSK extension") + } + identitiesLength -= 2 + + if identityLength > identitiesLength { + return 0, errors.New("tls: invalid PSK extension") + } + + var identity []byte + if !s.ReadBytes(&identity, int(identityLength)) { + return 0, errors.New("tls: invalid PSK extension") + } + + identitiesLength -= identityLength // identity + + var obfuscatedTicketAge uint32 + if !s.ReadUint32(&obfuscatedTicketAge) { + return 0, errors.New("tls: invalid PSK extension") + } + + e.PskIdentities = append(e.PskIdentities, PskIdentity{ + Label: identity, + ObfuscatedTicketAge: obfuscatedTicketAge, + }) + + identitiesLength -= 4 // obfuscated ticket age + } + + var bindersLength uint16 + if !s.ReadUint16(&bindersLength) { + return 0, errors.New("tls: invalid PSK extension") + } + + // binders + for bindersLength > 0 { + var binderLength uint8 + if !s.ReadUint8(&binderLength) { + return 0, errors.New("tls: invalid PSK extension") + } + bindersLength -= 1 + + if uint16(binderLength) > bindersLength { + return 0, errors.New("tls: invalid PSK extension") + } + + var binder []byte + if !s.ReadBytes(&binder, int(binderLength)) { + return 0, errors.New("tls: invalid PSK extension") + } + + e.PskBinders = append(e.PskBinders, binder) + + bindersLength -= uint16(binderLength) + } + + return fullLen, nil +}