package tls
import (
"bytes"
"compress/zlib"
"context"
"errors"
"fmt"
"io"
"github.com/andybalholm/brotli"
"github.com/klauspost/compress/zstd"
"github.com/refraction-networking/utls/internal/fips140tls"
"github.com/refraction-networking/utls/internal/hpke"
"github.com/refraction-networking/utls/internal/tls13"
)
func (hs *clientHandshakeStateTLS13 ) utlsReadServerCertificate (msg any ) (processedMsg any , err error ) {
for _ , ext := range hs .uconn .Extensions {
switch ext .(type ) {
case *UtlsCompressCertExtension :
if len (hs .uconn .certCompressionAlgs ) > 0 {
compressedCertMsg , ok := msg .(*utlsCompressedCertificateMsg )
if ok {
if err = transcriptMsg (compressedCertMsg , hs .transcript ); err != nil {
return nil , err
}
msg , err = hs .decompressCert (*compressedCertMsg )
if err != nil {
return nil , fmt .Errorf ("tls: failed to decompress certificate message: %w" , err )
} else {
return msg , nil
}
}
}
default :
continue
}
}
return nil , nil
}
func (hs *clientHandshakeStateTLS13 ) decompressCert (m utlsCompressedCertificateMsg ) (*certificateMsgTLS13 , error ) {
var (
decompressed io .Reader
compressed = bytes .NewReader (m .compressedCertificateMessage )
c = hs .c
)
supportedAlg := false
for _ , alg := range hs .uconn .certCompressionAlgs {
if m .algorithm == uint16 (alg ) {
supportedAlg = true
}
}
if !supportedAlg {
c .sendAlert (alertBadCertificate )
return nil , fmt .Errorf ("unadvertised algorithm (%d)" , m .algorithm )
}
switch CertCompressionAlgo (m .algorithm ) {
case CertCompressionBrotli :
decompressed = brotli .NewReader (compressed )
case CertCompressionZlib :
rc , err := zlib .NewReader (compressed )
if err != nil {
c .sendAlert (alertBadCertificate )
return nil , fmt .Errorf ("failed to open zlib reader: %w" , err )
}
defer rc .Close ()
decompressed = rc
case CertCompressionZstd :
rc , err := zstd .NewReader (compressed )
if err != nil {
c .sendAlert (alertBadCertificate )
return nil , fmt .Errorf ("failed to open zstd reader: %w" , err )
}
defer rc .Close ()
decompressed = rc
default :
c .sendAlert (alertBadCertificate )
return nil , fmt .Errorf ("unsupported algorithm (%d)" , m .algorithm )
}
rawMsg := make ([]byte , m .uncompressedLength +4 )
rawMsg [0 ] = typeCertificate
rawMsg [1 ] = uint8 (m .uncompressedLength >> 16 )
rawMsg [2 ] = uint8 (m .uncompressedLength >> 8 )
rawMsg [3 ] = uint8 (m .uncompressedLength )
n , err := decompressed .Read (rawMsg [4 :])
if err != nil && !errors .Is (err , io .EOF ) {
c .sendAlert (alertBadCertificate )
return nil , err
}
if n < len (rawMsg )-4 {
c .sendAlert (alertBadCertificate )
return nil , fmt .Errorf ("decompressed len (%d) does not match specified len (%d)" , n , m .uncompressedLength )
}
certMsg := new (certificateMsgTLS13 )
if !certMsg .unmarshal (rawMsg ) {
return nil , c .sendAlert (alertUnexpectedMessage )
}
return certMsg , nil
}
func (hs *clientHandshakeStateTLS13 ) serverFinishedReceived () error {
if err := hs .sendClientEncryptedExtensions (); err != nil {
return err
}
return nil
}
func (hs *clientHandshakeStateTLS13 ) sendClientEncryptedExtensions () error {
c := hs .c
clientEncryptedExtensions := new (utlsClientEncryptedExtensionsMsg )
if c .utls .applicationSettingsCodepoint != 0 {
clientEncryptedExtensions .applicationSettingsCodepoint = c .utls .applicationSettingsCodepoint
clientEncryptedExtensions .applicationSettings = c .utls .localApplicationSettings
if _ , err := c .writeHandshakeRecord (clientEncryptedExtensions , hs .transcript ); err != nil {
return err
}
}
return nil
}
func (hs *clientHandshakeStateTLS13 ) utlsReadServerParameters (encryptedExtensions *encryptedExtensionsMsg ) error {
hs .c .utls .peerApplicationSettings = encryptedExtensions .utls .applicationSettings
hs .c .utls .applicationSettingsCodepoint = encryptedExtensions .utls .applicationSettingsCodepoint
if hs .c .utls .applicationSettingsCodepoint != 0 {
if hs .uconn .vers < VersionTLS13 {
return errors .New ("tls: server sent application settings at invalid version" )
}
if len (hs .uconn .clientProtocol ) == 0 {
return errors .New ("tls: server sent application settings without ALPN" )
}
if alps , ok := hs .uconn .config .ApplicationSettings [hs .serverHello .alpnProtocol ]; ok {
hs .c .utls .localApplicationSettings = alps
} else {
return nil
}
}
return nil
}
func (c *Conn ) makeClientHelloForApplyPreset () (*clientHelloMsg , *keySharePrivateKeys , *echClientContext , error ) {
config := c .config
if len (config .ServerName ) == 0 && !config .InsecureSkipVerify && len (config .InsecureServerNameToVerify ) == 0 {
return nil , nil , nil , errors .New ("tls: at least one of ServerName, InsecureSkipVerify or InsecureServerNameToVerify must be specified in the tls.Config" )
}
nextProtosLength := 0
for _ , proto := range config .NextProtos {
if l := len (proto ); l == 0 || l > 255 {
return nil , nil , nil , errors .New ("tls: invalid NextProtos value" )
} else {
nextProtosLength += 1 + l
}
}
if nextProtosLength > 0xffff {
return nil , nil , nil , errors .New ("tls: NextProtos values too large" )
}
supportedVersions := config .supportedVersions (roleClient )
if len (supportedVersions ) == 0 {
return nil , nil , nil , errors .New ("tls: no supported versions satisfy MinVersion and MaxVersion" )
}
maxVersion := config .maxSupportedVersion (roleClient )
hello := &clientHelloMsg {
vers : maxVersion ,
compressionMethods : []uint8 {compressionNone },
random : make ([]byte , 32 ),
extendedMasterSecret : true ,
ocspStapling : true ,
scts : true ,
serverName : hostnameInSNI (config .ServerName ),
supportedCurves : config .curvePreferences (maxVersion ),
supportedPoints : []uint8 {pointFormatUncompressed },
secureRenegotiationSupported : true ,
alpnProtocols : config .NextProtos ,
supportedVersions : supportedVersions ,
}
if hello .vers > VersionTLS12 {
hello .vers = VersionTLS12
}
if c .handshakes > 0 {
hello .secureRenegotiation = c .clientFinished [:]
}
preferenceOrder := cipherSuitesPreferenceOrder
if !hasAESGCMHardwareSupport {
preferenceOrder = cipherSuitesPreferenceOrderNoAES
}
configCipherSuites := config .cipherSuites ()
hello .cipherSuites = make ([]uint16 , 0 , len (configCipherSuites ))
for _ , suiteId := range preferenceOrder {
suite := mutualCipherSuite (configCipherSuites , suiteId )
if suite == nil {
continue
}
if maxVersion < VersionTLS12 && suite .flags &suiteTLS12 != 0 {
continue
}
hello .cipherSuites = append (hello .cipherSuites , suiteId )
}
_ , err := io .ReadFull (config .rand (), hello .random )
if err != nil {
return nil , nil , nil , errors .New ("tls: short read from Rand: " + err .Error())
}
if c .quic == nil {
hello .sessionId = make ([]byte , 32 )
if _ , err := io .ReadFull (config .rand (), hello .sessionId ); err != nil {
return nil , nil , nil , errors .New ("tls: short read from Rand: " + err .Error())
}
}
if maxVersion >= VersionTLS12 {
hello .supportedSignatureAlgorithms = supportedSignatureAlgorithms ()
}
if testingOnlyForceClientHelloSignatureAlgorithms != nil {
hello .supportedSignatureAlgorithms = testingOnlyForceClientHelloSignatureAlgorithms
}
var keyShareKeys *keySharePrivateKeys
if hello .supportedVersions [0 ] == VersionTLS13 {
if len (hello .supportedVersions ) == 1 {
hello .cipherSuites = nil
}
if fips140tls .Required () {
hello .cipherSuites = append (hello .cipherSuites , defaultCipherSuitesTLS13FIPS ...)
} else if hasAESGCMHardwareSupport {
hello .cipherSuites = append (hello .cipherSuites , defaultCipherSuitesTLS13 ...)
} else {
hello .cipherSuites = append (hello .cipherSuites , defaultCipherSuitesTLS13NoAES ...)
}
if len (hello .supportedCurves ) == 0 {
return nil , nil , nil , errors .New ("tls: no supported elliptic curves for ECDHE" )
}
}
var ech *echClientContext
if c .config .EncryptedClientHelloConfigList != nil {
if c .config .MinVersion != 0 && c .config .MinVersion < VersionTLS13 {
return nil , nil , nil , errors .New ("tls: MinVersion must be >= VersionTLS13 if EncryptedClientHelloConfigList is populated" )
}
if c .config .MaxVersion != 0 && c .config .MaxVersion <= VersionTLS12 {
return nil , nil , nil , errors .New ("tls: MaxVersion must be >= VersionTLS13 if EncryptedClientHelloConfigList is populated" )
}
echConfigs , err := parseECHConfigList (c .config .EncryptedClientHelloConfigList )
if err != nil {
return nil , nil , nil , err
}
echConfig := pickECHConfig (echConfigs )
if echConfig == nil {
return nil , nil , nil , errors .New ("tls: EncryptedClientHelloConfigList contains no valid configs" )
}
ech = &echClientContext {config : echConfig }
hello .encryptedClientHello = []byte {1 }
hello .supportedPoints = nil
hello .ticketSupported = false
hello .secureRenegotiationSupported = false
hello .extendedMasterSecret = false
echPK , err := hpke .ParseHPKEPublicKey (ech .config .KemID , ech .config .PublicKey )
if err != nil {
return nil , nil , nil , err
}
suite , err := pickECHCipherSuite (ech .config .SymmetricCipherSuite )
if err != nil {
return nil , nil , nil , err
}
ech .kdfID , ech .aeadID = suite .KDFID , suite .AEADID
info := append ([]byte ("tls ech\x00" ), ech .config .raw ...)
ech .encapsulatedKey , ech .hpkeContext , err = hpke .SetupSender (ech .config .KemID , suite .KDFID , suite .AEADID , echPK , info )
if err != nil {
return nil , nil , nil , err
}
}
return hello , keyShareKeys , ech , nil
}
func (c *UConn ) clientHandshake (ctx context .Context ) (err error ) {
hello := c .HandshakeState .Hello .getPrivatePtr ()
ech := c .echCtx
defer func () { c .HandshakeState .Hello = hello .getPublicPtr () }()
sessionIsLocked := c .utls .sessionController .isSessionLocked ()
if c .config == nil {
c .config = defaultConfig ()
}
c .didResume = false
if len (c .config .ServerName ) == 0 && !c .config .InsecureSkipVerify && len (c .config .InsecureServerNameToVerify ) == 0 {
return errors .New ("tls: at least one of ServerName, InsecureSkipVerify or InsecureServerNameToVerify must be specified in the tls.Config" )
}
nextProtosLength := 0
for _ , proto := range c .config .NextProtos {
if l := len (proto ); l == 0 || l > 255 {
return errors .New ("tls: invalid NextProtos value" )
} else {
nextProtosLength += 1 + l
}
}
if nextProtosLength > 0xffff {
return errors .New ("tls: NextProtos values too large" )
}
if c .handshakes > 0 {
hello .secureRenegotiation = c .clientFinished [:]
}
var (
session *SessionState
earlySecret *tls13 .EarlySecret
binderKey []byte
)
if !sessionIsLocked {
session , earlySecret , binderKey , err = c .loadSession (hello )
} else {
session = c .HandshakeState .Session
if c .HandshakeState .State13 .EarlySecret != nil && session != nil {
cipherSuite := cipherSuiteTLS13ByID (session .cipherSuite )
earlySecret = tls13 .NewEarlySecretFromSecret (cipherSuite .hash .New , c .HandshakeState .State13 .EarlySecret )
}
binderKey = c .HandshakeState .State13 .BinderKey
}
if err != nil {
return err
}
if session != nil {
defer func () {
if err != nil {
if cacheKey := c .clientSessionCacheKey (); cacheKey != "" {
c .config .ClientSessionCache .Put (cacheKey , nil )
}
}
}()
}
if ech != nil && c .clientHelloBuildStatus != BuildByUtls {
ech .innerHello = hello .clone ()
hello .serverName = string (ech .config .PublicName )
hello .random = make ([]byte , 32 )
_, err = io .ReadFull (c .config .rand (), hello .random )
if err != nil {
return errors .New ("tls: short read from Rand: " + err .Error())
}
if err := computeAndUpdateOuterECHExtension (hello , ech .innerHello , ech , true ); err != nil {
return err
}
}
c .serverName = hello .serverName
if _ , err := c .writeHandshakeRecord (hello , nil ); err != nil {
return err
}
if hello .earlyData {
suite := cipherSuiteTLS13ByID (session .cipherSuite )
transcript := suite .hash .New ()
if err := transcriptMsg (hello , transcript ); err != nil {
return err
}
earlyTrafficSecret := earlySecret .ClientEarlyTrafficSecret (transcript )
c .quicSetWriteSecret (QUICEncryptionLevelEarly , suite .id , earlyTrafficSecret )
}
msg , err := c .readHandshake (nil )
if err != nil {
return err
}
serverHello , ok := msg .(*serverHelloMsg )
if !ok {
c .sendAlert (alertUnexpectedMessage )
return unexpectedMessageError (serverHello , msg )
}
if err := c .pickTLSVersion (serverHello ); err != nil {
return err
}
maxVers := c .config .maxSupportedVersion (roleClient )
tls12Downgrade := string (serverHello .random [24 :]) == downgradeCanaryTLS12
tls11Downgrade := string (serverHello .random [24 :]) == downgradeCanaryTLS11
if maxVers == VersionTLS13 && c .vers <= VersionTLS12 && (tls12Downgrade || tls11Downgrade ) ||
maxVers == VersionTLS12 && c .vers <= VersionTLS11 && tls11Downgrade {
c .sendAlert (alertIllegalParameter )
return errors .New ("tls: downgrade attempt detected, possibly due to a MitM attack or a broken middlebox" )
}
c .HandshakeState .ServerHello = serverHello .getPublicPtr ()
if c .vers == VersionTLS13 {
hs13 := c .HandshakeState .toPrivate13 ()
hs13 .serverHello = serverHello
hs13 .hello = hello
hs13 .echContext = ech
if c .HandshakeState .State13 .EarlySecret != nil && session .cipherSuite != 0 {
hs13 .earlySecret = tls13 .NewEarlySecretFromSecret (cipherSuiteTLS13ByID (session .cipherSuite ).hash .New , c .HandshakeState .State13 .EarlySecret )
}
if c .HandshakeState .MasterSecret != nil && session .cipherSuite != 0 {
hs13 .masterSecret = tls13 .NewMasterSecretFromSecret (cipherSuiteTLS13ByID (session .cipherSuite ).hash .New , c .HandshakeState .MasterSecret )
}
if !sessionIsLocked {
hs13 .earlySecret = earlySecret
hs13 .binderKey = binderKey
hs13 .session = session
}
hs13 .ctx = ctx
err = hs13 .handshake ()
if handshakeState := hs13 .toPublic13 (); handshakeState != nil {
c .HandshakeState = *handshakeState
}
return err
}
hs12 := c .HandshakeState .toPrivate12 ()
hs12 .serverHello = serverHello
hs12 .hello = hello
hs12 .ctx = ctx
hs12 .session = session
err = hs12 .handshake ()
if handshakeState := hs12 .toPublic12 (); handshakeState != nil {
c .HandshakeState = *handshakeState
}
if err != nil {
return err
}
return nil
}
func (c *UConn ) echTranscriptMsg (outer *clientHelloMsg , echCtx *echClientContext ) (err error ) {
encodedInner , err := encodeInnerClientHelloReorderOuterExts (echCtx .innerHello , int (echCtx .config .MaxNameLength ), c .extensionsList ())
if err != nil {
return err
}
decodedInner , err := decodeInnerClientHello (outer , encodedInner )
if err != nil {
return err
}
if err := transcriptMsg (decodedInner , echCtx .innerTranscript ); err != nil {
return err
}
return nil
}
The pages are generated with Golds v0.8.4 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .