mirror of
https://github.com/refraction-networking/utls.git
synced 2025-04-01 19:17:36 +03:00
feat: Convert raw bytes or tlsfingerprint record to ClientHelloSpec (#168)
* feat: byte to clienthellospecs conversion * feat: specific case for GREASE and ALPS Will automatically add "h2" to ALPS and write to log when GREASE extension is imported in `ImportTLSClientHello()` * fix: ReadCompressionMethods ReadCompressionMethods didn't advance the s and fails reading extensions * fix: remove debug log * fix: use cryptobyte for internal helper `helper.Uint8to16()` now calls `(*cryptobyte.String).ReadUint16()` * fix: preshared key fingerprinter test updated fingerprinter test to test with PreSharedKey extension * fix: naming of FakePreSharedKeyExt It is a Fake extension since `crypto/tls` doesn't really implement PSK-based resumption and neither do we. * feat: Properly check GREASE Adopted from #148. Co-Authored-By: gfw-report <gfw.report@protonmail.com> * feat: add fakeExtensionEncryptThenMAC And reordered `fakeExtensionDelegatedCredentials`. The new `Fingerprinter` is expected to account for the `fakeExtensionEncryptThenMAC` using a `GenericExtension` when `allowBluntMimicry` is set. Co-Authored-By: gfw-report <gfw.report@protonmail.com> * fix: remove keepPSK and minor - Removed all presence of keepPSK flag. - Added check before using the field of a map. --------- Co-authored-by: gfw-report <gfw.report@protonmail.com>
This commit is contained in:
parent
71b4ad3909
commit
dae72adb81
10 changed files with 961 additions and 366 deletions
|
@ -150,7 +150,8 @@ const (
|
||||||
|
|
||||||
// TLS CertificateStatusType (RFC 3546)
|
// TLS CertificateStatusType (RFC 3546)
|
||||||
const (
|
const (
|
||||||
statusTypeOCSP uint8 = 1
|
statusTypeOCSP uint8 = 1
|
||||||
|
statusV2TypeOCSP uint8 = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
// Certificate types (for certificateRequestMsg)
|
// Certificate types (for certificateRequestMsg)
|
||||||
|
|
|
@ -305,7 +305,7 @@ func (m *clientHelloMsg) marshal() ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// marshalWithoutBinders returns the ClientHello through the
|
// 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.
|
// 4.2.11.2. Note that m.pskBinders must be set to slices of the correct length.
|
||||||
func (m *clientHelloMsg) marshalWithoutBinders() ([]byte, error) {
|
func (m *clientHelloMsg) marshalWithoutBinders() ([]byte, error) {
|
||||||
bindersLen := 2 // uint16 length prefix
|
bindersLen := 2 // uint16 length prefix
|
||||||
|
|
23
internal/helper/typeconv.go
Normal file
23
internal/helper/typeconv.go
Normal file
|
@ -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
|
||||||
|
}
|
272
u_common.go
272
u_common.go
|
@ -7,8 +7,14 @@ package tls
|
||||||
import (
|
import (
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash"
|
"hash"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/refraction-networking/utls/internal/helper"
|
||||||
|
"golang.org/x/crypto/cryptobyte"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Naming convention:
|
// Naming convention:
|
||||||
|
@ -35,10 +41,12 @@ const (
|
||||||
utlsFakeExtensionCustom uint16 = 1234 // not IANA assigned, for ALPS
|
utlsFakeExtensionCustom uint16 = 1234 // not IANA assigned, for ALPS
|
||||||
|
|
||||||
// extensions with 'fake' prefix break connection, if server echoes them back
|
// extensions with 'fake' prefix break connection, if server echoes them back
|
||||||
|
fakeExtensionEncryptThenMAC uint16 = 22
|
||||||
fakeExtensionTokenBinding uint16 = 24
|
fakeExtensionTokenBinding uint16 = 24
|
||||||
|
fakeExtensionDelegatedCredentials uint16 = 34
|
||||||
|
fakeExtensionPreSharedKey uint16 = 41
|
||||||
fakeOldExtensionChannelID uint16 = 30031 // not IANA assigned
|
fakeOldExtensionChannelID uint16 = 30031 // not IANA assigned
|
||||||
fakeExtensionChannelID uint16 = 30032 // not IANA assigned
|
fakeExtensionChannelID uint16 = 30032 // not IANA assigned
|
||||||
fakeExtensionDelegatedCredentials uint16 = 34
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -161,6 +169,268 @@ type ClientHelloSpec struct {
|
||||||
// TLSFingerprintLink string // ?? link to tlsfingerprint.io for informational purposes
|
// 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 (
|
var (
|
||||||
// HelloGolang will use default "crypto/tls" handshake marshaling codepath, which WILL
|
// HelloGolang will use default "crypto/tls" handshake marshaling codepath, which WILL
|
||||||
// overwrite your changes to Hello(Config, Session are fine).
|
// overwrite your changes to Hello(Config, Session are fine).
|
||||||
|
|
|
@ -599,7 +599,7 @@ func (uconn *UConn) SetTLSVers(minTLSVers, maxTLSVers uint16, specExtensions []T
|
||||||
minVers := uint16(0)
|
minVers := uint16(0)
|
||||||
maxVers := uint16(0)
|
maxVers := uint16(0)
|
||||||
for _, vers := range versions {
|
for _, vers := range versions {
|
||||||
if vers == GREASE_PLACEHOLDER {
|
if isGREASEUint16(vers) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if maxVers < vers || maxVers == 0 {
|
if maxVers < vers || maxVers == 0 {
|
||||||
|
|
|
@ -6,16 +6,12 @@ package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/cryptobyte"
|
"golang.org/x/crypto/cryptobyte"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Fingerprinter is a struct largely for holding options for the FingerprintClientHello func
|
// Fingerprinter is a struct largely for holding options for the FingerprintClientHello func
|
||||||
type Fingerprinter struct {
|
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
|
// AllowBluntMimicry will ensure that unknown extensions are
|
||||||
// passed along into the resulting ClientHelloSpec as-is
|
// passed along into the resulting ClientHelloSpec as-is
|
||||||
// It will not ensure that the PSK is passed along, if you require that, use KeepPSK
|
// 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
|
// 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-6.2
|
||||||
// https://tools.ietf.org/html/rfc5246#section-7.4
|
// https://tools.ietf.org/html/rfc5246#section-7.4
|
||||||
func (f *Fingerprinter) FingerprintClientHello(data []byte) (*ClientHelloSpec, error) {
|
func (f *Fingerprinter) FingerprintClientHello(data []byte) (clientHelloSpec *ClientHelloSpec, err error) {
|
||||||
clientHelloSpec := &ClientHelloSpec{}
|
clientHelloSpec = &ClientHelloSpec{}
|
||||||
s := cryptobyte.String(data)
|
s := cryptobyte.String(data)
|
||||||
|
|
||||||
var contentType uint8
|
var contentType uint8
|
||||||
|
@ -75,23 +71,25 @@ func (f *Fingerprinter) FingerprintClientHello(data []byte) (*ClientHelloSpec, e
|
||||||
return nil, errors.New("unable to read session id")
|
return nil, errors.New("unable to read session id")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CipherSuites
|
||||||
var cipherSuitesBytes cryptobyte.String
|
var cipherSuitesBytes cryptobyte.String
|
||||||
if !s.ReadUint16LengthPrefixed(&cipherSuitesBytes) {
|
if !s.ReadUint16LengthPrefixed(&cipherSuitesBytes) {
|
||||||
return nil, errors.New("unable to read ciphersuites")
|
return nil, errors.New("unable to read ciphersuites")
|
||||||
}
|
}
|
||||||
cipherSuites := []uint16{}
|
err = clientHelloSpec.ReadCipherSuites(cipherSuitesBytes)
|
||||||
for !cipherSuitesBytes.Empty() {
|
if err != nil {
|
||||||
var suite uint16
|
return nil, err
|
||||||
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) {
|
// CompressionMethods
|
||||||
|
var compressionMethods cryptobyte.String
|
||||||
|
if !s.ReadUint8LengthPrefixed(&compressionMethods) {
|
||||||
return nil, errors.New("unable to read compression methods")
|
return nil, errors.New("unable to read compression methods")
|
||||||
}
|
}
|
||||||
|
err = clientHelloSpec.ReadCompressionMethods(compressionMethods)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if s.Empty() {
|
if s.Empty() {
|
||||||
// ClientHello is optionally followed by extension data
|
// 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")
|
return nil, errors.New("unable to read extensions data")
|
||||||
}
|
}
|
||||||
|
|
||||||
for !extensions.Empty() {
|
err = clientHelloSpec.ReadTLSExtensions(extensions, f.AllowBluntMimicry)
|
||||||
var extension uint16
|
if err != nil {
|
||||||
var extData cryptobyte.String
|
return nil, err
|
||||||
if !extensions.ReadUint16(&extension) ||
|
|
||||||
!extensions.ReadUint16LengthPrefixed(&extData) {
|
|
||||||
return nil, errors.New("unable to read extension data")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch extension {
|
|
||||||
case extensionServerName:
|
|
||||||
// RFC 6066, Section 3
|
|
||||||
var nameList cryptobyte.String
|
|
||||||
if !extData.ReadUint16LengthPrefixed(&nameList) || nameList.Empty() {
|
|
||||||
return nil, errors.New("unable to read server name extension data")
|
|
||||||
}
|
|
||||||
var serverName string
|
|
||||||
for !nameList.Empty() {
|
|
||||||
var nameType uint8
|
|
||||||
var serverNameBytes cryptobyte.String
|
|
||||||
if !nameList.ReadUint8(&nameType) ||
|
|
||||||
!nameList.ReadUint16LengthPrefixed(&serverNameBytes) ||
|
|
||||||
serverNameBytes.Empty() {
|
|
||||||
return nil, errors.New("unable to read server name extension data")
|
|
||||||
}
|
|
||||||
if nameType != 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(serverName) != 0 {
|
|
||||||
return nil, errors.New("multiple names of the same name_type in server name extension are prohibited")
|
|
||||||
}
|
|
||||||
serverName = string(serverNameBytes)
|
|
||||||
if strings.HasSuffix(serverName, ".") {
|
|
||||||
return nil, errors.New("SNI value may not include a trailing dot")
|
|
||||||
}
|
|
||||||
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SNIExtension{})
|
|
||||||
|
|
||||||
}
|
|
||||||
case extensionNextProtoNeg:
|
|
||||||
// draft-agl-tls-nextprotoneg-04
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &NPNExtension{})
|
|
||||||
|
|
||||||
case extensionStatusRequest:
|
|
||||||
// RFC 4366, Section 3.6
|
|
||||||
var statusType uint8
|
|
||||||
var ignored cryptobyte.String
|
|
||||||
if !extData.ReadUint8(&statusType) ||
|
|
||||||
!extData.ReadUint16LengthPrefixed(&ignored) ||
|
|
||||||
!extData.ReadUint16LengthPrefixed(&ignored) {
|
|
||||||
return nil, errors.New("unable to read status request extension data")
|
|
||||||
}
|
|
||||||
|
|
||||||
if statusType == statusTypeOCSP {
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &StatusRequestExtension{})
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("status request extension statusType is not statusTypeOCSP")
|
|
||||||
}
|
|
||||||
|
|
||||||
case extensionSupportedCurves:
|
|
||||||
// RFC 4492, sections 5.1.1 and RFC 8446, Section 4.2.7
|
|
||||||
var curvesBytes cryptobyte.String
|
|
||||||
if !extData.ReadUint16LengthPrefixed(&curvesBytes) || curvesBytes.Empty() {
|
|
||||||
return nil, errors.New("unable to read supported curves extension data")
|
|
||||||
}
|
|
||||||
curves := []CurveID{}
|
|
||||||
for !curvesBytes.Empty() {
|
|
||||||
var curve uint16
|
|
||||||
if !curvesBytes.ReadUint16(&curve) {
|
|
||||||
return nil, errors.New("unable to read supported curves extension data")
|
|
||||||
}
|
|
||||||
curves = append(curves, CurveID(unGREASEUint16(curve)))
|
|
||||||
}
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SupportedCurvesExtension{curves})
|
|
||||||
|
|
||||||
case extensionSupportedPoints:
|
|
||||||
// RFC 4492, Section 5.1.2
|
|
||||||
supportedPoints := []uint8{}
|
|
||||||
if !readUint8LengthPrefixed(&extData, &supportedPoints) ||
|
|
||||||
len(supportedPoints) == 0 {
|
|
||||||
return nil, errors.New("unable to read supported points extension data")
|
|
||||||
}
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SupportedPointsExtension{supportedPoints})
|
|
||||||
|
|
||||||
case extensionSessionTicket:
|
|
||||||
// RFC 5077, Section 3.2
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SessionTicketExtension{})
|
|
||||||
|
|
||||||
case extensionSignatureAlgorithms:
|
|
||||||
// RFC 5246, Section 7.4.1.4.1
|
|
||||||
var sigAndAlgs cryptobyte.String
|
|
||||||
if !extData.ReadUint16LengthPrefixed(&sigAndAlgs) || sigAndAlgs.Empty() {
|
|
||||||
return nil, errors.New("unable to read signature algorithms extension data")
|
|
||||||
}
|
|
||||||
supportedSignatureAlgorithms := []SignatureScheme{}
|
|
||||||
for !sigAndAlgs.Empty() {
|
|
||||||
var sigAndAlg uint16
|
|
||||||
if !sigAndAlgs.ReadUint16(&sigAndAlg) {
|
|
||||||
return nil, errors.New("unable to read signature algorithms extension data")
|
|
||||||
}
|
|
||||||
supportedSignatureAlgorithms = append(
|
|
||||||
supportedSignatureAlgorithms, SignatureScheme(sigAndAlg))
|
|
||||||
}
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SignatureAlgorithmsExtension{supportedSignatureAlgorithms})
|
|
||||||
|
|
||||||
case extensionSignatureAlgorithmsCert:
|
|
||||||
// RFC 8446, Section 4.2.3
|
|
||||||
if f.AllowBluntMimicry {
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("unsupported extension SignatureAlgorithmsCert")
|
|
||||||
}
|
|
||||||
|
|
||||||
case extensionRenegotiationInfo:
|
|
||||||
// RFC 5746, Section 3.2
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &RenegotiationInfoExtension{RenegotiateOnceAsClient})
|
|
||||||
|
|
||||||
case extensionALPN:
|
|
||||||
// RFC 7301, Section 3.1
|
|
||||||
var protoList cryptobyte.String
|
|
||||||
if !extData.ReadUint16LengthPrefixed(&protoList) || protoList.Empty() {
|
|
||||||
return nil, errors.New("unable to read ALPN extension data")
|
|
||||||
}
|
|
||||||
alpnProtocols := []string{}
|
|
||||||
for !protoList.Empty() {
|
|
||||||
var proto cryptobyte.String
|
|
||||||
if !protoList.ReadUint8LengthPrefixed(&proto) || proto.Empty() {
|
|
||||||
return nil, errors.New("unable to read ALPN extension data")
|
|
||||||
}
|
|
||||||
alpnProtocols = append(alpnProtocols, string(proto))
|
|
||||||
|
|
||||||
}
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &ALPNExtension{alpnProtocols})
|
|
||||||
|
|
||||||
case extensionSCT:
|
|
||||||
// RFC 6962, Section 3.3.1
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SCTExtension{})
|
|
||||||
|
|
||||||
case extensionSupportedVersions:
|
|
||||||
// RFC 8446, Section 4.2.1
|
|
||||||
var versList cryptobyte.String
|
|
||||||
if !extData.ReadUint8LengthPrefixed(&versList) || versList.Empty() {
|
|
||||||
return nil, errors.New("unable to read supported versions extension data")
|
|
||||||
}
|
|
||||||
supportedVersions := []uint16{}
|
|
||||||
for !versList.Empty() {
|
|
||||||
var vers uint16
|
|
||||||
if !versList.ReadUint16(&vers) {
|
|
||||||
return nil, errors.New("unable to read supported versions extension data")
|
|
||||||
}
|
|
||||||
supportedVersions = append(supportedVersions, unGREASEUint16(vers))
|
|
||||||
}
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SupportedVersionsExtension{supportedVersions})
|
|
||||||
// If SupportedVersionsExtension is present, use that instead of record+handshake versions
|
|
||||||
clientHelloSpec.TLSVersMin = 0
|
|
||||||
clientHelloSpec.TLSVersMax = 0
|
|
||||||
|
|
||||||
case extensionKeyShare:
|
|
||||||
// RFC 8446, Section 4.2.8
|
|
||||||
var clientShares cryptobyte.String
|
|
||||||
if !extData.ReadUint16LengthPrefixed(&clientShares) {
|
|
||||||
return nil, errors.New("unable to read key share extension data")
|
|
||||||
}
|
|
||||||
keyShares := []KeyShare{}
|
|
||||||
for !clientShares.Empty() {
|
|
||||||
var ks KeyShare
|
|
||||||
var group uint16
|
|
||||||
if !clientShares.ReadUint16(&group) ||
|
|
||||||
!readUint16LengthPrefixed(&clientShares, &ks.Data) ||
|
|
||||||
len(ks.Data) == 0 {
|
|
||||||
return nil, errors.New("unable to read key share extension data")
|
|
||||||
}
|
|
||||||
ks.Group = CurveID(unGREASEUint16(group))
|
|
||||||
// if not GREASE, key share data will be discarded as it should
|
|
||||||
// be generated per connection
|
|
||||||
if ks.Group != GREASE_PLACEHOLDER {
|
|
||||||
ks.Data = nil
|
|
||||||
}
|
|
||||||
keyShares = append(keyShares, ks)
|
|
||||||
}
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &KeyShareExtension{keyShares})
|
|
||||||
|
|
||||||
case extensionPSKModes:
|
|
||||||
// RFC 8446, Section 4.2.9
|
|
||||||
// TODO: PSK Modes have their own form of GREASE-ing which is not currently implemented
|
|
||||||
// the current functionality will NOT re-GREASE/re-randomize these values when using a fingerprinted spec
|
|
||||||
// https://github.com/refraction-networking/utls/pull/58#discussion_r522354105
|
|
||||||
// https://tools.ietf.org/html/draft-ietf-tls-grease-01#section-2
|
|
||||||
pskModes := []uint8{}
|
|
||||||
if !readUint8LengthPrefixed(&extData, &pskModes) {
|
|
||||||
return nil, errors.New("unable to read PSK extension data")
|
|
||||||
}
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &PSKKeyExchangeModesExtension{pskModes})
|
|
||||||
|
|
||||||
case utlsExtensionExtendedMasterSecret:
|
|
||||||
// https://tools.ietf.org/html/rfc7627
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsExtendedMasterSecretExtension{})
|
|
||||||
|
|
||||||
case utlsExtensionPadding:
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle})
|
|
||||||
|
|
||||||
case utlsExtensionCompressCertificate:
|
|
||||||
methods := []CertCompressionAlgo{}
|
|
||||||
methodsRaw := new(cryptobyte.String)
|
|
||||||
if !extData.ReadUint8LengthPrefixed(methodsRaw) {
|
|
||||||
return nil, errors.New("unable to read cert compression algorithms extension data")
|
|
||||||
}
|
|
||||||
for !methodsRaw.Empty() {
|
|
||||||
var method uint16
|
|
||||||
if !methodsRaw.ReadUint16(&method) {
|
|
||||||
return nil, errors.New("unable to read cert compression algorithms extension data")
|
|
||||||
}
|
|
||||||
methods = append(methods, CertCompressionAlgo(method))
|
|
||||||
}
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsCompressCertExtension{methods})
|
|
||||||
|
|
||||||
case fakeExtensionChannelID:
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &FakeChannelIDExtension{})
|
|
||||||
|
|
||||||
case fakeOldExtensionChannelID:
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &FakeChannelIDExtension{true})
|
|
||||||
|
|
||||||
case fakeExtensionTokenBinding:
|
|
||||||
var tokenBindingExt FakeTokenBindingExtension
|
|
||||||
var keyParameters cryptobyte.String
|
|
||||||
if !extData.ReadUint8(&tokenBindingExt.MajorVersion) ||
|
|
||||||
!extData.ReadUint8(&tokenBindingExt.MinorVersion) ||
|
|
||||||
!extData.ReadUint8LengthPrefixed(&keyParameters) {
|
|
||||||
return nil, errors.New("unable to read token binding extension data")
|
|
||||||
}
|
|
||||||
tokenBindingExt.KeyParameters = keyParameters
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &tokenBindingExt)
|
|
||||||
|
|
||||||
case utlsExtensionApplicationSettings:
|
|
||||||
// Similar to ALPN (RFC 7301, Section 3.1):
|
|
||||||
// https://datatracker.ietf.org/doc/html/draft-vvv-tls-alps#section-3
|
|
||||||
var protoList cryptobyte.String
|
|
||||||
if !extData.ReadUint16LengthPrefixed(&protoList) || protoList.Empty() {
|
|
||||||
return nil, errors.New("unable to read ALPS extension data")
|
|
||||||
}
|
|
||||||
supportedProtocols := []string{}
|
|
||||||
for !protoList.Empty() {
|
|
||||||
var proto cryptobyte.String
|
|
||||||
if !protoList.ReadUint8LengthPrefixed(&proto) || proto.Empty() {
|
|
||||||
return nil, errors.New("unable to read ALPS extension data")
|
|
||||||
}
|
|
||||||
supportedProtocols = append(supportedProtocols, string(proto))
|
|
||||||
}
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &ApplicationSettingsExtension{supportedProtocols})
|
|
||||||
|
|
||||||
case fakeRecordSizeLimit:
|
|
||||||
recordSizeExt := new(FakeRecordSizeLimitExtension)
|
|
||||||
if !extData.ReadUint16(&recordSizeExt.Limit) {
|
|
||||||
return nil, errors.New("unable to read record size limit extension data")
|
|
||||||
}
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, recordSizeExt)
|
|
||||||
|
|
||||||
case fakeExtensionDelegatedCredentials:
|
|
||||||
//https://datatracker.ietf.org/doc/html/draft-ietf-tls-subcerts-15#section-4.1.1
|
|
||||||
var supportedAlgs cryptobyte.String
|
|
||||||
if !extData.ReadUint16LengthPrefixed(&supportedAlgs) || supportedAlgs.Empty() {
|
|
||||||
return nil, errors.New("unable to read signature algorithms extension data")
|
|
||||||
}
|
|
||||||
supportedSignatureAlgorithms := []SignatureScheme{}
|
|
||||||
for !supportedAlgs.Empty() {
|
|
||||||
var sigAndAlg uint16
|
|
||||||
if !supportedAlgs.ReadUint16(&sigAndAlg) {
|
|
||||||
return nil, errors.New("unable to read signature algorithms extension data")
|
|
||||||
}
|
|
||||||
supportedSignatureAlgorithms = append(
|
|
||||||
supportedSignatureAlgorithms, SignatureScheme(sigAndAlg))
|
|
||||||
}
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &FakeDelegatedCredentialsExtension{supportedSignatureAlgorithms})
|
|
||||||
|
|
||||||
case extensionPreSharedKey:
|
|
||||||
// RFC 8446, Section 4.2.11
|
|
||||||
if f.KeepPSK {
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("unsupported extension PreSharedKey")
|
|
||||||
}
|
|
||||||
|
|
||||||
case extensionCookie:
|
|
||||||
// RFC 8446, Section 4.2.2
|
|
||||||
if f.AllowBluntMimicry {
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("unsupported extension Cookie")
|
|
||||||
}
|
|
||||||
|
|
||||||
case extensionEarlyData:
|
|
||||||
// RFC 8446, Section 4.2.10
|
|
||||||
if f.AllowBluntMimicry {
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("unsupported extension EarlyData")
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
if isGREASEUint16(extension) {
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsGREASEExtension{unGREASEUint16(extension), extData})
|
|
||||||
} else if f.AllowBluntMimicry {
|
|
||||||
clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("unsupported extension %d", extension)
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.AlwaysAddPadding {
|
if f.AlwaysAddPadding {
|
||||||
alreadyHasPadding := false
|
clientHelloSpec.AlwaysAddPadding()
|
||||||
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
|
return clientHelloSpec, nil
|
||||||
|
|
|
@ -501,12 +501,6 @@ func TestUTLSFingerprintClientHelloKeepPSK(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
f := &Fingerprinter{}
|
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)
|
generatedSpec, err := f.FingerprintClientHello(helloBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("got error: %v; expected to succeed", err)
|
t.Errorf("got error: %v; expected to succeed", err)
|
||||||
|
@ -514,10 +508,8 @@ func TestUTLSFingerprintClientHelloKeepPSK(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ext := range generatedSpec.Extensions {
|
for _, ext := range generatedSpec.Extensions {
|
||||||
if genericExtension, ok := (ext).(*GenericExtension); ok {
|
if _, ok := (ext).(*FakePreSharedKeyExtension); ok {
|
||||||
if genericExtension.Id == extensionPreSharedKey {
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.Errorf("generated ClientHelloSpec with KeepPSK does not include preshared key extension")
|
t.Errorf("generated ClientHelloSpec with KeepPSK does not include preshared key extension")
|
||||||
|
|
|
@ -1988,7 +1988,7 @@ func (uconn *UConn) ApplyPreset(p *ClientHelloSpec) error {
|
||||||
hello.CipherSuites = make([]uint16, len(p.CipherSuites))
|
hello.CipherSuites = make([]uint16, len(p.CipherSuites))
|
||||||
copy(hello.CipherSuites, p.CipherSuites)
|
copy(hello.CipherSuites, p.CipherSuites)
|
||||||
for i := range hello.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)
|
hello.CipherSuites[i] = GetBoringGREASEValue(uconn.greaseSeed, ssl_grease_cipher)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2029,7 +2029,7 @@ func (uconn *UConn) ApplyPreset(p *ClientHelloSpec) error {
|
||||||
}
|
}
|
||||||
case *SupportedCurvesExtension:
|
case *SupportedCurvesExtension:
|
||||||
for i := range ext.Curves {
|
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))
|
ext.Curves[i] = CurveID(GetBoringGREASEValue(uconn.greaseSeed, ssl_grease_group))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2037,7 +2037,7 @@ func (uconn *UConn) ApplyPreset(p *ClientHelloSpec) error {
|
||||||
preferredCurveIsSet := false
|
preferredCurveIsSet := false
|
||||||
for i := range ext.KeyShares {
|
for i := range ext.KeyShares {
|
||||||
curveID := ext.KeyShares[i].Group
|
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))
|
ext.KeyShares[i].Group = CurveID(GetBoringGREASEValue(uconn.greaseSeed, ssl_grease_group))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -2059,7 +2059,7 @@ func (uconn *UConn) ApplyPreset(p *ClientHelloSpec) error {
|
||||||
}
|
}
|
||||||
case *SupportedVersionsExtension:
|
case *SupportedVersionsExtension:
|
||||||
for i := range ext.Versions {
|
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)
|
ext.Versions[i] = GetBoringGREASEValue(uconn.greaseSeed, ssl_grease_version)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
32
u_public.go
32
u_public.go
|
@ -344,7 +344,7 @@ type PubClientHelloMsg struct {
|
||||||
KeyShares []KeyShare
|
KeyShares []KeyShare
|
||||||
EarlyData bool
|
EarlyData bool
|
||||||
PskModes []uint8
|
PskModes []uint8
|
||||||
PskIdentities []pskIdentity
|
PskIdentities []PskIdentity
|
||||||
PskBinders [][]byte
|
PskBinders [][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -379,7 +379,7 @@ func (chm *PubClientHelloMsg) getPrivatePtr() *clientHelloMsg {
|
||||||
keyShares: KeyShares(chm.KeyShares).ToPrivate(),
|
keyShares: KeyShares(chm.KeyShares).ToPrivate(),
|
||||||
earlyData: chm.EarlyData,
|
earlyData: chm.EarlyData,
|
||||||
pskModes: chm.PskModes,
|
pskModes: chm.PskModes,
|
||||||
pskIdentities: chm.PskIdentities,
|
pskIdentities: PskIdentities(chm.PskIdentities).ToPrivate(),
|
||||||
pskBinders: chm.PskBinders,
|
pskBinders: chm.PskBinders,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -416,7 +416,7 @@ func (chm *clientHelloMsg) getPublicPtr() *PubClientHelloMsg {
|
||||||
KeyShares: keyShares(chm.keyShares).ToPublic(),
|
KeyShares: keyShares(chm.keyShares).ToPublic(),
|
||||||
EarlyData: chm.earlyData,
|
EarlyData: chm.earlyData,
|
||||||
PskModes: chm.pskModes,
|
PskModes: chm.pskModes,
|
||||||
PskIdentities: chm.pskIdentities,
|
PskIdentities: pskIdentities(chm.pskIdentities).ToPublic(),
|
||||||
PskBinders: chm.pskBinders,
|
PskBinders: chm.pskBinders,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -562,6 +562,32 @@ func (KSS KeyShares) ToPrivate() []keyShare {
|
||||||
return kss
|
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 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.
|
// ClientSessionState contains the state needed by clients to resume TLS sessions.
|
||||||
|
|
|
@ -7,8 +7,79 @@ package tls
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"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 {
|
type TLSExtension interface {
|
||||||
writeToUConn(*UConn) error
|
writeToUConn(*UConn) error
|
||||||
|
|
||||||
|
@ -19,6 +90,16 @@ type TLSExtension interface {
|
||||||
Read(p []byte) (n int, err error) // implements io.Reader
|
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 {
|
type NPNExtension struct {
|
||||||
NextProtos []string
|
NextProtos []string
|
||||||
}
|
}
|
||||||
|
@ -43,6 +124,12 @@ func (e *NPNExtension) Read(b []byte) (int, error) {
|
||||||
return e.Len(), io.EOF
|
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 {
|
type SNIExtension struct {
|
||||||
ServerName string // not an array because go crypto/tls doesn't support multiple SNIs
|
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
|
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 {
|
type StatusRequestExtension struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,6 +238,26 @@ func (e *StatusRequestExtension) Read(b []byte) (int, error) {
|
||||||
return e.Len(), io.EOF
|
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 {
|
type StatusRequestV2Extension struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,6 +288,28 @@ func (e *StatusRequestV2Extension) Read(b []byte) (int, error) {
|
||||||
return e.Len(), io.EOF
|
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 {
|
type SupportedCurvesExtension struct {
|
||||||
Curves []CurveID
|
Curves []CurveID
|
||||||
}
|
}
|
||||||
|
@ -177,6 +342,26 @@ func (e *SupportedCurvesExtension) Read(b []byte) (int, error) {
|
||||||
return e.Len(), io.EOF
|
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 {
|
type SupportedPointsExtension struct {
|
||||||
SupportedPoints []uint8
|
SupportedPoints []uint8
|
||||||
}
|
}
|
||||||
|
@ -206,6 +391,19 @@ func (e *SupportedPointsExtension) Read(b []byte) (int, error) {
|
||||||
return e.Len(), io.EOF
|
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 {
|
type SignatureAlgorithmsExtension struct {
|
||||||
SupportedSignatureAlgorithms []SignatureScheme
|
SupportedSignatureAlgorithms []SignatureScheme
|
||||||
}
|
}
|
||||||
|
@ -237,6 +435,27 @@ func (e *SignatureAlgorithmsExtension) Read(b []byte) (int, error) {
|
||||||
return e.Len(), io.EOF
|
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 {
|
type SignatureAlgorithmsCertExtension struct {
|
||||||
SupportedSignatureAlgorithms []SignatureScheme
|
SupportedSignatureAlgorithms []SignatureScheme
|
||||||
}
|
}
|
||||||
|
@ -268,6 +487,30 @@ func (e *SignatureAlgorithmsCertExtension) Read(b []byte) (int, error) {
|
||||||
return e.Len(), io.EOF
|
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 {
|
type RenegotiationInfoExtension struct {
|
||||||
// Renegotiation field limits how many times client will perform renegotiation: no limit, once, or never.
|
// 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.
|
// 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
|
return e.Len(), io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *RenegotiationInfoExtension) Write(_ []byte) (int, error) {
|
||||||
|
e.Renegotiation = RenegotiateOnceAsClient
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
type ALPNExtension struct {
|
type ALPNExtension struct {
|
||||||
AlpnProtocols []string
|
AlpnProtocols []string
|
||||||
}
|
}
|
||||||
|
@ -356,6 +604,27 @@ func (e *ALPNExtension) Read(b []byte) (int, error) {
|
||||||
return e.Len(), io.EOF
|
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.
|
// ApplicationSettingsExtension represents the TLS ALPS extension.
|
||||||
// At the time of this writing, this extension is currently a draft:
|
// At the time of this writing, this extension is currently a draft:
|
||||||
// https://datatracker.ietf.org/doc/html/draft-vvv-tls-alps-01
|
// 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
|
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 {
|
type SCTExtension struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -428,6 +719,10 @@ func (e *SCTExtension) Read(b []byte) (int, error) {
|
||||||
return e.Len(), io.EOF
|
return e.Len(), io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *SCTExtension) Write(_ []byte) (int, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
type SessionTicketExtension struct {
|
type SessionTicketExtension struct {
|
||||||
Session *ClientSessionState
|
Session *ClientSessionState
|
||||||
}
|
}
|
||||||
|
@ -464,6 +759,11 @@ func (e *SessionTicketExtension) Read(b []byte) (int, error) {
|
||||||
return e.Len(), io.EOF
|
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.
|
// GenericExtension allows to include in ClientHello arbitrary unsupported extensions.
|
||||||
type GenericExtension struct {
|
type GenericExtension struct {
|
||||||
Id uint16
|
Id uint16
|
||||||
|
@ -518,6 +818,11 @@ func (e *UtlsExtendedMasterSecretExtension) Read(b []byte) (int, error) {
|
||||||
return e.Len(), io.EOF
|
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")
|
var extendedMasterSecretLabel = []byte("extended master secret")
|
||||||
|
|
||||||
// extendedMasterFromPreMasterSecret generates the master secret from the pre-master
|
// 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
|
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 {
|
type UtlsPaddingExtension struct {
|
||||||
PaddingLen int
|
PaddingLen int
|
||||||
WillPad bool // set to false to disable extension
|
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
|
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
|
// UtlsCompressCertExtension is only implemented client-side, for server certificates. Alternate
|
||||||
// certificate message formats (https://datatracker.ietf.org/doc/html/rfc7250) are not supported.
|
// 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
|
return e.Len(), io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/google/boringssl/blob/7d7554b6b3c79e707e25521e61e066ce2b996e4c/ssl/t1_lib.c#L2803
|
func (e *UtlsCompressCertExtension) Write(b []byte) (int, error) {
|
||||||
func BoringPaddingStyle(unpaddedLen int) (int, bool) {
|
fullLen := len(b)
|
||||||
if unpaddedLen > 0xff && unpaddedLen < 0x200 {
|
extData := cryptobyte.String(b)
|
||||||
paddingLen := 0x200 - unpaddedLen
|
methods := []CertCompressionAlgo{}
|
||||||
if paddingLen >= 4+1 {
|
methodsRaw := new(cryptobyte.String)
|
||||||
paddingLen -= 4
|
if !extData.ReadUint8LengthPrefixed(methodsRaw) {
|
||||||
} else {
|
return 0, errors.New("unable to read cert compression algorithms extension data")
|
||||||
paddingLen = 1
|
|
||||||
}
|
|
||||||
return paddingLen, true
|
|
||||||
}
|
}
|
||||||
return 0, false
|
for !methodsRaw.Empty() {
|
||||||
|
var method uint16
|
||||||
|
if !methodsRaw.ReadUint16(&method) {
|
||||||
|
return 0, errors.New("unable to read cert compression algorithms extension data")
|
||||||
|
}
|
||||||
|
methods = append(methods, CertCompressionAlgo(method))
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Algorithms = methods
|
||||||
|
return fullLen, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TLS 1.3 */
|
/* TLS 1.3 */
|
||||||
|
@ -724,6 +1061,35 @@ func (e *KeyShareExtension) Read(b []byte) (int, error) {
|
||||||
return e.Len(), io.EOF
|
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 {
|
func (e *KeyShareExtension) writeToUConn(uc *UConn) error {
|
||||||
uc.HandshakeState.Hello.KeyShares = e.KeyShares
|
uc.HandshakeState.Hello.KeyShares = e.KeyShares
|
||||||
return nil
|
return nil
|
||||||
|
@ -761,6 +1127,22 @@ func (e *PSKKeyExchangeModesExtension) Read(b []byte) (int, error) {
|
||||||
return e.Len(), io.EOF
|
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 {
|
func (e *PSKKeyExchangeModesExtension) writeToUConn(uc *UConn) error {
|
||||||
uc.HandshakeState.Hello.PskModes = e.Modes
|
uc.HandshakeState.Hello.PskModes = e.Modes
|
||||||
return nil
|
return nil
|
||||||
|
@ -803,6 +1185,26 @@ func (e *SupportedVersionsExtension) Read(b []byte) (int, error) {
|
||||||
return e.Len(), io.EOF
|
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
|
// MUST NOT be part of initial ClientHello
|
||||||
type CookieExtension struct {
|
type CookieExtension struct {
|
||||||
Cookie []byte
|
Cookie []byte
|
||||||
|
@ -863,6 +1265,10 @@ func (e *FakeChannelIDExtension) Read(b []byte) (int, error) {
|
||||||
return e.Len(), io.EOF
|
return e.Len(), io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *FakeChannelIDExtension) Write(_ []byte) (int, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
type FakeRecordSizeLimitExtension struct {
|
type FakeRecordSizeLimitExtension struct {
|
||||||
Limit uint16
|
Limit uint16
|
||||||
}
|
}
|
||||||
|
@ -891,6 +1297,15 @@ func (e *FakeRecordSizeLimitExtension) Read(b []byte) (int, error) {
|
||||||
return e.Len(), io.EOF
|
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 {
|
type DelegatedCredentialsExtension struct {
|
||||||
AlgorithmsSignature []SignatureScheme
|
AlgorithmsSignature []SignatureScheme
|
||||||
}
|
}
|
||||||
|
@ -921,7 +1336,6 @@ func (e *DelegatedCredentialsExtension) Read(b []byte) (int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc8472#section-2
|
// https://tools.ietf.org/html/rfc8472#section-2
|
||||||
|
|
||||||
type FakeTokenBindingExtension struct {
|
type FakeTokenBindingExtension struct {
|
||||||
MajorVersion, MinorVersion uint8
|
MajorVersion, MinorVersion uint8
|
||||||
KeyParameters []uint8
|
KeyParameters []uint8
|
||||||
|
@ -954,6 +1368,19 @@ func (e *FakeTokenBindingExtension) Read(b []byte) (int, error) {
|
||||||
return e.Len(), io.EOF
|
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
|
// https://datatracker.ietf.org/doc/html/draft-ietf-tls-subcerts-15#section-4.1.1
|
||||||
|
|
||||||
type FakeDelegatedCredentialsExtension struct {
|
type FakeDelegatedCredentialsExtension struct {
|
||||||
|
@ -985,3 +1412,175 @@ func (e *FakeDelegatedCredentialsExtension) Read(b []byte) (int, error) {
|
||||||
}
|
}
|
||||||
return e.Len(), io.EOF
|
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
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue