feat: byte to clienthellospecs conversion

This commit is contained in:
Gaukas Wang 2023-02-26 01:07:49 -07:00
parent d5cdf5dbe1
commit fb742616ca
No known key found for this signature in database
GPG key ID: 9E2F8986D76F8B5D
6 changed files with 896 additions and 349 deletions

View file

@ -151,7 +151,8 @@ const (
// TLS CertificateStatusType (RFC 3546)
const (
statusTypeOCSP uint8 = 1
statusTypeOCSP uint8 = 1
statusV2TypeOCSP uint8 = 2
)
// Certificate types (for certificateRequestMsg)

View file

@ -0,0 +1,35 @@
package helper
import (
"bytes"
"encoding/binary"
"io"
)
func ReadUint16(r io.Reader, v *uint16) error {
var buf [2]byte
if _, err := io.ReadFull(r, buf[:]); err != nil {
return err
}
*v = binary.BigEndian.Uint16(buf[:])
return nil
}
// 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) {
reader := bytes.NewReader(in)
var out []uint16
for {
var v uint16
err := ReadUint16(reader, &v)
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
out = append(out, v)
}
return out, nil
}

View file

@ -7,8 +7,13 @@ package tls
import (
"crypto/hmac"
"crypto/sha512"
"errors"
"fmt"
"hash"
"log"
"github.com/refraction-networking/utls/internal/helper"
"golang.org/x/crypto/cryptobyte"
)
// Naming convention:
@ -155,6 +160,208 @@ 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(b []byte) error {
s := cryptobyte.String(b)
if !readUint8LengthPrefixed(&s, &chs.CompressionMethods) {
return errors.New("unable to read compression methods")
}
return nil
}
// ReadTLSExtensions is a helper function to construct a list of TLS extensions from
// a byte slice into []TLSExtension.
func (chs *ClientHelloSpec) ReadTLSExtensions(b []byte, keepPSK, allowBluntMimicry bool) error {
extensions := cryptobyte.String(b)
for !extensions.Empty() {
var extension uint16
var extData cryptobyte.String
if !extensions.ReadUint16(&extension) ||
!extensions.ReadUint16LengthPrefixed(&extData) {
return errors.New("unable to read extension data")
}
if extension == extensionPreSharedKey && !keepPSK {
continue // skip PSK, this will result in fingerprint change!!!!
}
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.(*PreSharedKeyExtension); 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, keepPSK, allowBluntMimicry bool) error {
var tlsExtensionTypes []uint16
var err error
chs.CipherSuites, err = helper.Uint8to16(data["cipher_suites"])
if err != nil {
return err
}
chs.CompressionMethods = data["compression_methods"]
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", extType)
chs.Extensions = append(chs.Extensions, &GenericExtension{extType, []byte{}})
} else {
if extType == extensionSupportedVersions {
chs.TLSVersMin = 0
chs.TLSVersMax = 0
}
switch extType {
case extensionSupportedPoints:
_, err = extension.Write(data["pt_fmts"])
if err != nil {
return err
}
case extensionSignatureAlgorithms:
_, err = extension.Write(data["sig_algs"])
if err != nil {
return err
}
case extensionSupportedVersions:
// 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:
_, err = extension.Write(data["curves"])
if err != nil {
return err
}
case extensionALPN:
_, err = extension.Write(data["alpn"])
if err != nil {
return err
}
case extensionKeyShare:
// 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:
// 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:
// 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:
_, err = extension.Write(data["record_size_limit"]) // uint16 as []byte
if err != nil {
return err
}
}
chs.Extensions = append(chs.Extensions, extension)
}
}
return nil
}
var (
// HelloGolang will use default "crypto/tls" handshake marshaling codepath, which WILL
// overwrite your changes to Hello(Config, Session are fine).

View file

@ -6,8 +6,6 @@ package tls
import (
"errors"
"fmt"
"strings"
"golang.org/x/crypto/cryptobyte"
)
@ -40,8 +38,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,22 +73,20 @@ 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) {
return nil, errors.New("unable to read compression methods")
// CompressionMethods
err = clientHelloSpec.ReadCompressionMethods(s)
if err != nil {
return nil, err
}
if s.Empty() {
@ -103,327 +99,10 @@ 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
}
}
clientHelloSpec.ReadTLSExtensions(extensions, f.KeepPSK, f.AllowBluntMimicry)
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

View file

@ -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.

View file

@ -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 extensionPreSharedKey:
return &PreSharedKeyExtension{}
// 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
}
return paddingLen, true
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")
}
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 */
@ -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
}
// PreSharedKeyExtension is an extension used to set the PSK extension in the
// ClientHello.
//
// Warning: detailed test pending. not production ready. Should be treated as
// a FakeExtension.
type PreSharedKeyExtension struct {
PskIdentities []PskIdentity
PskBinders [][]byte
}
func (e *PreSharedKeyExtension) writeToUConn(uc *UConn) error {
uc.HandshakeState.Hello.PskIdentities = e.PskIdentities
uc.HandshakeState.Hello.PskBinders = e.PskBinders
return nil
}
func (e *PreSharedKeyExtension) 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 *PreSharedKeyExtension) 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 *PreSharedKeyExtension) 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
}