package tls
import (
"bufio"
"bytes"
"context"
"crypto/cipher"
"encoding/binary"
"errors"
"fmt"
"hash"
"net"
"slices"
"strconv"
"golang.org/x/crypto/cryptobyte"
)
type ClientHelloBuildStatus int
const NotBuilt ClientHelloBuildStatus = 0
const BuildByUtls ClientHelloBuildStatus = 1
const BuildByGoTLS ClientHelloBuildStatus = 2
type UConn struct {
*Conn
Extensions []TLSExtension
ClientHelloID ClientHelloID
sessionController *sessionController
clientHelloBuildStatus ClientHelloBuildStatus
clientHelloSpec *ClientHelloSpec
HandshakeState PubClientHandshakeState
greaseSeed [ssl_grease_last_index ]uint16
omitSNIExtension bool
skipResumptionOnNilExtension bool
certCompressionAlgs []CertCompressionAlgo
ech ECHExtension
echCtx *echClientContext
}
func UClient (conn net .Conn , config *Config , clientHelloID ClientHelloID ) *UConn {
if config == nil {
config = &Config {}
}
tlsConn := Conn {conn : conn , config : config , isClient : true }
handshakeState := PubClientHandshakeState {C : &tlsConn , Hello : &PubClientHelloMsg {}}
uconn := UConn {Conn : &tlsConn , ClientHelloID : clientHelloID , HandshakeState : handshakeState }
uconn .HandshakeState .uconn = &uconn
uconn .handshakeFn = uconn .clientHandshake
uconn .sessionController = newSessionController (&uconn )
uconn .utls .sessionController = uconn .sessionController
uconn .skipResumptionOnNilExtension = config .PreferSkipResumptionOnNilExtension || clientHelloID .Client != helloCustom
return &uconn
}
func (uconn *UConn ) BuildHandshakeState () error {
return uconn .buildHandshakeState (true )
}
func (uconn *UConn ) BuildHandshakeStateWithoutSession () error {
return uconn .buildHandshakeState (false )
}
func (uconn *UConn ) buildHandshakeState (loadSession bool ) error {
if uconn .ClientHelloID == HelloGolang {
if uconn .clientHelloBuildStatus == BuildByGoTLS {
return nil
}
uAssert (uconn .clientHelloBuildStatus == NotBuilt , "BuildHandshakeState failed: invalid call, client hello has already been built by utls" )
hello , keySharePrivate , ech , err := uconn .makeClientHello ()
if err != nil {
return err
}
uconn .HandshakeState .Hello = hello .getPublicPtr ()
uconn .HandshakeState .State13 .KeyShareKeys = keySharePrivate .ToPublic ()
uconn .HandshakeState .C = uconn .Conn
uconn .echCtx = ech
uconn .clientHelloBuildStatus = BuildByGoTLS
} else {
uAssert (uconn .clientHelloBuildStatus == BuildByUtls || uconn .clientHelloBuildStatus == NotBuilt , "BuildHandshakeState failed: invalid call, client hello has already been built by go-tls" )
if uconn .clientHelloBuildStatus == NotBuilt {
err := uconn .applyPresetByID (uconn .ClientHelloID )
if err != nil {
return err
}
if uconn .omitSNIExtension {
uconn .removeSNIExtension ()
}
}
err := uconn .ApplyConfig ()
if err != nil {
return err
}
if loadSession {
err = uconn .uLoadSession ()
if err != nil {
return err
}
}
err = uconn .MarshalClientHello ()
if err != nil {
return err
}
if loadSession {
uconn .uApplyPatch ()
uconn .sessionController .finalCheck ()
uconn .clientHelloBuildStatus = BuildByUtls
}
}
return nil
}
func (uconn *UConn ) uLoadSession () error {
if cfg := uconn .config ; cfg .SessionTicketsDisabled || cfg .ClientSessionCache == nil {
return nil
}
switch uconn .sessionController .shouldLoadSession () {
case shouldReturn :
case shouldSetTicket :
uconn .sessionController .setSessionTicketToUConn ()
case shouldSetPsk :
uconn .sessionController .setPskToUConn ()
case shouldLoad :
hello := uconn .HandshakeState .Hello .getPrivatePtr ()
uconn .sessionController .utlsAboutToLoadSession ()
session , earlySecret , binderKey , err := uconn .loadSession (hello )
if session == nil || err != nil {
return err
}
if session .version == VersionTLS12 {
uconn .sessionController .initSessionTicketExt (session , hello .sessionTicket )
uconn .sessionController .setSessionTicketToUConn ()
} else {
uconn .sessionController .initPskExt (session , earlySecret , binderKey , hello .pskIdentities )
}
}
return nil
}
func (uconn *UConn ) uApplyPatch () {
helloLen := len (uconn .HandshakeState .Hello .Raw )
if uconn .sessionController .shouldUpdateBinders () {
uconn .sessionController .updateBinders ()
uconn .sessionController .setPskToUConn ()
}
uAssert (helloLen == len (uconn .HandshakeState .Hello .Raw ), "tls: uApplyPatch Failed: the patch should never change the length of the marshaled clientHello" )
}
func (uconn *UConn ) DidTls12Resume () bool {
return uconn .didResume
}
func (uconn *UConn ) SetSessionState (session *ClientSessionState ) error {
sessionTicketExt := &SessionTicketExtension {Initialized : true }
if session != nil {
sessionTicketExt .Ticket = session .session .ticket
sessionTicketExt .Session = session .session
}
return uconn .SetSessionTicketExtension (sessionTicketExt )
}
func (uconn *UConn ) SetSessionTicketExtension (sessionTicketExt ISessionTicketExtension ) error {
if uconn .config .SessionTicketsDisabled || uconn .config .ClientSessionCache == nil {
return fmt .Errorf ("tls: SetSessionTicketExtension failed: session is disabled" )
}
if sessionTicketExt == nil {
return nil
}
return uconn .sessionController .overrideSessionTicketExt (sessionTicketExt )
}
func (uconn *UConn ) SetPskExtension (pskExt PreSharedKeyExtension ) error {
if uconn .config .SessionTicketsDisabled || uconn .config .ClientSessionCache == nil {
return fmt .Errorf ("tls: SetPskExtension failed: session is disabled" )
}
if pskExt == nil {
return nil
}
uconn .HandshakeState .Hello .TicketSupported = true
return uconn .sessionController .overridePskExt (pskExt )
}
func (uconn *UConn ) SetSessionCache (cache ClientSessionCache ) {
uconn .config .ClientSessionCache = cache
uconn .HandshakeState .Hello .TicketSupported = true
}
func (uconn *UConn ) SetClientRandom (r []byte ) error {
if len (r ) != 32 {
return errors .New ("Incorrect client random length! Expected: 32, got: " + strconv .Itoa (len (r )))
} else {
uconn .HandshakeState .Hello .Random = make ([]byte , 32 )
copy (uconn .HandshakeState .Hello .Random , r )
return nil
}
}
func (uconn *UConn ) SetSNI (sni string ) {
hname := hostnameInSNI (sni )
uconn .config .ServerName = hname
for _ , ext := range uconn .Extensions {
sniExt , ok := ext .(*SNIExtension )
if ok {
sniExt .ServerName = hname
}
}
}
func (uconn *UConn ) RemoveSNIExtension () error {
if uconn .ClientHelloID == HelloGolang {
return fmt .Errorf ("cannot call RemoveSNIExtension on a UConn with a HelloGolang ClientHelloID" )
}
uconn .omitSNIExtension = true
return nil
}
func (uconn *UConn ) removeSNIExtension () {
filteredExts := make ([]TLSExtension , 0 , len (uconn .Extensions ))
for _ , e := range uconn .Extensions {
if _ , ok := e .(*SNIExtension ); !ok {
filteredExts = append (filteredExts , e )
}
}
uconn .Extensions = filteredExts
}
func (c *UConn ) Handshake () error {
return c .HandshakeContext (context .Background ())
}
func (c *UConn ) HandshakeContext (ctx context .Context ) error {
return c .handshakeContext (ctx )
}
func (c *UConn ) handshakeContext (ctx context .Context ) (ret error ) {
if c .isHandshakeComplete .Load () {
return nil
}
handshakeCtx , cancel := context .WithCancel (ctx )
defer cancel ()
if c .quic != nil {
c .quic .cancelc = handshakeCtx .Done ()
c .quic .cancel = cancel
} else if ctx .Done () != nil {
done := make (chan struct {})
interruptRes := make (chan error , 1 )
defer func () {
close (done )
if ctxErr := <-interruptRes ; ctxErr != nil {
ret = ctxErr
}
}()
go func () {
select {
case <- handshakeCtx .Done ():
_ = c .conn .Close ()
interruptRes <- handshakeCtx .Err ()
case <- done :
interruptRes <- nil
}
}()
}
c .handshakeMutex .Lock ()
defer c .handshakeMutex .Unlock ()
if err := c .handshakeErr ; err != nil {
return err
}
if c .isHandshakeComplete .Load () {
return nil
}
c .in .Lock ()
defer c .in .Unlock ()
if c .isClient {
err := c .BuildHandshakeState ()
if err != nil {
return err
}
}
c .handshakeErr = c .handshakeFn (handshakeCtx )
if c .handshakeErr == nil {
c .handshakes ++
} else {
c .flush ()
}
if c .handshakeErr == nil && !c .isHandshakeComplete .Load () {
c .handshakeErr = errors .New ("tls: internal error: handshake should have had a result" )
}
if c .handshakeErr != nil && c .isHandshakeComplete .Load () {
panic ("tls: internal error: handshake returned an error but is marked successful" )
}
if c .quic != nil {
if c .handshakeErr == nil {
c .quicHandshakeComplete ()
c .quicSetReadSecret (QUICEncryptionLevelApplication , c .cipherSuite , c .in .trafficSecret )
} else {
var a alert
c .out .Lock ()
if !errors .As (c .out .err , &a ) {
a = alertInternalError
}
c .out .Unlock ()
c .handshakeErr = fmt .Errorf ("%w%.0w" , c .handshakeErr , AlertError (a ))
}
close (c .quic .blockedc )
close (c .quic .signalc )
}
return c .handshakeErr
}
func (c *UConn ) Write (b []byte ) (int , error ) {
for {
x := c .activeCall .Load ()
if x &1 != 0 {
return 0 , net .ErrClosed
}
if c .activeCall .CompareAndSwap (x , x +2 ) {
defer c .activeCall .Add (-2 )
break
}
}
if err := c .Handshake (); err != nil {
return 0 , err
}
c .out .Lock ()
defer c .out .Unlock ()
if err := c .out .err ; err != nil {
return 0 , err
}
if !c .isHandshakeComplete .Load () {
return 0 , alertInternalError
}
if c .closeNotifySent {
return 0 , errShutdown
}
var m int
if len (b ) > 1 && c .vers <= VersionTLS10 {
if _ , ok := c .out .cipher .(cipher .BlockMode ); ok {
n , err := c .writeRecordLocked (recordTypeApplicationData , b [:1 ])
if err != nil {
return n , c .out .setErrorLocked (err )
}
m , b = 1 , b [1 :]
}
}
n , err := c .writeRecordLocked (recordTypeApplicationData , b )
return n + m , c .out .setErrorLocked (err )
}
func (uconn *UConn ) ApplyConfig () error {
for _ , ext := range uconn .Extensions {
err := ext .writeToUConn (uconn )
if err != nil {
return err
}
}
return nil
}
func (uconn *UConn ) extensionsList () []uint16 {
outerExts := []uint16 {}
for _ , ext := range uconn .Extensions {
buffer := cryptobyte .String (make ([]byte , 2000 ))
ext .Read (buffer )
var extension uint16
buffer .ReadUint16 (&extension )
outerExts = append (outerExts , extension )
}
return outerExts
}
func (uconn *UConn ) computeAndUpdateOuterECHExtension (inner *clientHelloMsg , ech *echClientContext , useKey bool ) error {
var encapKey []byte
if useKey {
encapKey = ech .encapsulatedKey
}
encodedInner , err := encodeInnerClientHelloReorderOuterExts (inner , int (ech .config .MaxNameLength ), uconn .extensionsList ())
if err != nil {
return err
}
encryptedLen := len (encodedInner ) + 16
outerECHExt , err := generateOuterECHExt (ech .config .ConfigID , ech .kdfID , ech .aeadID , encapKey , make ([]byte , encryptedLen ))
if err != nil {
return err
}
echExtIdx := slices .IndexFunc (uconn .Extensions , func (ext TLSExtension ) bool {
_ , ok := ext .(EncryptedClientHelloExtension )
return ok
})
if echExtIdx < 0 {
return fmt .Errorf ("extension satisfying EncryptedClientHelloExtension not present" )
}
oldExt := uconn .Extensions [echExtIdx ]
uconn .Extensions [echExtIdx ] = &GenericExtension {
Id : extensionEncryptedClientHello ,
Data : outerECHExt ,
}
if err := uconn .MarshalClientHelloNoECH (); err != nil {
return err
}
serializedOuter := uconn .HandshakeState .Hello .Raw
serializedOuter = serializedOuter [4 :]
encryptedInner , err := ech .hpkeContext .Seal (serializedOuter , encodedInner )
if err != nil {
return err
}
outerECHExt , err = generateOuterECHExt (ech .config .ConfigID , ech .kdfID , ech .aeadID , encapKey , encryptedInner )
if err != nil {
return err
}
uconn .Extensions [echExtIdx ] = &GenericExtension {
Id : extensionEncryptedClientHello ,
Data : outerECHExt ,
}
if err := uconn .MarshalClientHelloNoECH (); err != nil {
return err
}
uconn .Extensions [echExtIdx ] = oldExt
return nil
}
func (uconn *UConn ) MarshalClientHello () error {
if len (uconn .config .EncryptedClientHelloConfigList ) > 0 {
inner , _ , ech , err := uconn .makeClientHello ()
if err != nil {
return err
}
inner .keyShares = KeyShares (uconn .HandshakeState .Hello .KeyShares ).ToPrivate ()
inner .supportedSignatureAlgorithms = uconn .HandshakeState .Hello .SupportedSignatureAlgorithms
inner .sessionId = uconn .HandshakeState .Hello .SessionId
inner .supportedCurves = uconn .HandshakeState .Hello .SupportedCurves
ech .innerHello = inner
uconn .computeAndUpdateOuterECHExtension (inner , ech , true )
uconn .echCtx = ech
return nil
}
if err := uconn .MarshalClientHelloNoECH (); err != nil {
return err
}
return nil
}
func (uconn *UConn ) MarshalClientHelloNoECH () error {
hello := uconn .HandshakeState .Hello
headerLength := 2 + 32 + 1 + len (hello .SessionId ) +
2 + len (hello .CipherSuites )*2 +
1 + len (hello .CompressionMethods )
extensionsLen := 0
var paddingExt *UtlsPaddingExtension
for _ , ext := range uconn .Extensions {
if pe , ok := ext .(*UtlsPaddingExtension ); !ok {
extensionsLen += ext .Len ()
} else {
if paddingExt == nil {
paddingExt = pe
} else {
return errors .New ("multiple padding extensions" )
}
}
}
if paddingExt != nil {
paddingExt .Update (headerLength + 4 + extensionsLen + 2 )
extensionsLen += paddingExt .Len ()
}
helloLen := headerLength
if len (uconn .Extensions ) > 0 {
helloLen += 2 + extensionsLen
}
helloBuffer := bytes .Buffer {}
bufferedWriter := bufio .NewWriterSize (&helloBuffer , helloLen +4 )
binary .Write (bufferedWriter , binary .BigEndian , typeClientHello )
helloLenBytes := []byte {byte (helloLen >> 16 ), byte (helloLen >> 8 ), byte (helloLen )}
binary .Write (bufferedWriter , binary .BigEndian , helloLenBytes )
binary .Write (bufferedWriter , binary .BigEndian , hello .Vers )
binary .Write (bufferedWriter , binary .BigEndian , hello .Random )
binary .Write (bufferedWriter , binary .BigEndian , uint8 (len (hello .SessionId )))
binary .Write (bufferedWriter , binary .BigEndian , hello .SessionId )
binary .Write (bufferedWriter , binary .BigEndian , uint16 (len (hello .CipherSuites )<<1 ))
for _ , suite := range hello .CipherSuites {
binary .Write (bufferedWriter , binary .BigEndian , suite )
}
binary .Write (bufferedWriter , binary .BigEndian , uint8 (len (hello .CompressionMethods )))
binary .Write (bufferedWriter , binary .BigEndian , hello .CompressionMethods )
if len (uconn .Extensions ) > 0 {
binary .Write (bufferedWriter , binary .BigEndian , uint16 (extensionsLen ))
for _ , ext := range uconn .Extensions {
if _ , err := bufferedWriter .ReadFrom (ext ); err != nil {
return err
}
}
}
err := bufferedWriter .Flush ()
if err != nil {
return err
}
if helloBuffer .Len () != 4 +helloLen {
return errors .New ("utls: unexpected ClientHello length. Expected: " + strconv .Itoa (4 +helloLen ) +
". Got: " + strconv .Itoa (helloBuffer .Len ()))
}
hello .Raw = helloBuffer .Bytes ()
return nil
}
func (uconn *UConn ) GetOutKeystream (length int ) ([]byte , error ) {
zeros := make ([]byte , length )
if outCipher , ok := uconn .out .cipher .(cipher .AEAD ); ok {
return outCipher .Seal (nil , uconn .out .seq [:], zeros , nil ), nil
}
return nil , errors .New ("could not convert OutCipher to cipher.AEAD" )
}
func (uconn *UConn ) SetTLSVers (minTLSVers , maxTLSVers uint16 , specExtensions []TLSExtension ) error {
if minTLSVers == 0 && maxTLSVers == 0 {
supportedVersionsExtensionsPresent := 0
for _ , e := range specExtensions {
switch ext := e .(type ) {
case *SupportedVersionsExtension :
findVersionsInSupportedVersionsExtensions := func (versions []uint16 ) (uint16 , uint16 ) {
minVers := uint16 (0 )
maxVers := uint16 (0 )
for _ , vers := range versions {
if isGREASEUint16 (vers ) {
continue
}
if maxVers < vers || maxVers == 0 {
maxVers = vers
}
if minVers > vers || minVers == 0 {
minVers = vers
}
}
return minVers , maxVers
}
supportedVersionsExtensionsPresent += 1
minTLSVers , maxTLSVers = findVersionsInSupportedVersionsExtensions (ext .Versions )
if minTLSVers == 0 && maxTLSVers == 0 {
return fmt .Errorf ("SupportedVersions extension has invalid Versions field" )
}
}
}
switch supportedVersionsExtensionsPresent {
case 0 :
minTLSVers = VersionTLS10
maxTLSVers = VersionTLS12
case 1 :
default :
return fmt .Errorf ("uconn.Extensions contains %v separate SupportedVersions extensions" ,
supportedVersionsExtensionsPresent )
}
}
if minTLSVers < VersionTLS10 || minTLSVers > VersionTLS13 {
return fmt .Errorf ("uTLS does not support 0x%X as min version" , minTLSVers )
}
if maxTLSVers < VersionTLS10 || maxTLSVers > VersionTLS13 {
return fmt .Errorf ("uTLS does not support 0x%X as max version" , maxTLSVers )
}
uconn .HandshakeState .Hello .SupportedVersions = makeSupportedVersions (minTLSVers , maxTLSVers )
if uconn .config .EncryptedClientHelloConfigList == nil {
uconn .config .MinVersion = minTLSVers
uconn .config .MaxVersion = maxTLSVers
}
return nil
}
func (uconn *UConn ) SetUnderlyingConn (c net .Conn ) {
uconn .Conn .conn = c
}
func (uconn *UConn ) GetUnderlyingConn () net .Conn {
return uconn .Conn .conn
}
func MakeConnWithCompleteHandshake (tcpConn net .Conn , version uint16 , cipherSuite uint16 , masterSecret []byte , clientRandom []byte , serverRandom []byte , isClient bool ) *Conn {
tlsConn := &Conn {conn : tcpConn , config : &Config {}, isClient : isClient }
cs := cipherSuiteByID (cipherSuite )
if cs != nil {
clientMAC , serverMAC , clientKey , serverKey , clientIV , serverIV :=
keysFromMasterSecret (version , cs , masterSecret , clientRandom , serverRandom ,
cs .macLen , cs .keyLen , cs .ivLen )
var clientCipher , serverCipher interface {}
var clientHash , serverHash hash .Hash
if cs .cipher != nil {
clientCipher = cs .cipher (clientKey , clientIV , true )
clientHash = cs .mac (clientMAC )
serverCipher = cs .cipher (serverKey , serverIV , false )
serverHash = cs .mac (serverMAC )
} else {
clientCipher = cs .aead (clientKey , clientIV )
serverCipher = cs .aead (serverKey , serverIV )
}
if isClient {
tlsConn .in .prepareCipherSpec (version , serverCipher , serverHash )
tlsConn .out .prepareCipherSpec (version , clientCipher , clientHash )
} else {
tlsConn .in .prepareCipherSpec (version , clientCipher , clientHash )
tlsConn .out .prepareCipherSpec (version , serverCipher , serverHash )
}
tlsConn .isHandshakeComplete .Store (true )
tlsConn .cipherSuite = cipherSuite
tlsConn .haveVers = true
tlsConn .vers = version
tlsConn .in .changeCipherSpec ()
tlsConn .out .changeCipherSpec ()
tlsConn .in .incSeq ()
tlsConn .out .incSeq ()
return tlsConn
} else {
return nil
}
}
func makeSupportedVersions (minVers , maxVers uint16 ) []uint16 {
a := make ([]uint16 , maxVers -minVers +1 )
for i := range a {
a [i ] = maxVers - uint16 (i )
}
return a
}
func (c *Conn ) utlsHandshakeMessageType (msgType byte ) (handshakeMessage , error ) {
switch msgType {
case utlsTypeCompressedCertificate :
return new (utlsCompressedCertificateMsg ), nil
case utlsTypeEncryptedExtensions :
if c .isClient {
return new (encryptedExtensionsMsg ), nil
} else {
return new (utlsClientEncryptedExtensionsMsg ), nil
}
default :
return nil , c .in .setErrorLocked (c .sendAlert (alertUnexpectedMessage ))
}
}
func (c *Conn ) utlsConnectionStateLocked (state *ConnectionState ) {
state .PeerApplicationSettings = c .utls .peerApplicationSettings
}
type utlsConnExtraFields struct {
peerApplicationSettings []byte
localApplicationSettings []byte
applicationSettingsCodepoint uint16
sessionController *sessionController
}
func (c *UConn ) Read (b []byte ) (int , error ) {
if err := c .Handshake (); err != nil {
return 0 , err
}
if len (b ) == 0 {
return 0 , nil
}
c .in .Lock ()
defer c .in .Unlock ()
for c .input .Len () == 0 {
if err := c .readRecord (); err != nil {
return 0 , err
}
for c .hand .Len () > 0 {
if err := c .handlePostHandshakeMessage (); err != nil {
return 0 , err
}
}
}
n , _ := c .input .Read (b )
if n != 0 && c .input .Len () == 0 && c .rawInput .Len () > 0 &&
recordType (c .rawInput .Bytes ()[0 ]) == recordTypeAlert {
if err := c .readRecord (); err != nil {
return n , err
}
}
return n , nil
}
func (c *UConn ) handleRenegotiation () error {
if c .vers == VersionTLS13 {
return errors .New ("tls: internal error: unexpected renegotiation" )
}
msg , err := c .readHandshake (nil )
if err != nil {
return err
}
helloReq , ok := msg .(*helloRequestMsg )
if !ok {
c .sendAlert (alertUnexpectedMessage )
return unexpectedMessageError (helloReq , msg )
}
if !c .isClient {
return c .sendAlert (alertNoRenegotiation )
}
switch c .config .Renegotiation {
case RenegotiateNever :
return c .sendAlert (alertNoRenegotiation )
case RenegotiateOnceAsClient :
if c .handshakes > 1 {
return c .sendAlert (alertNoRenegotiation )
}
case RenegotiateFreelyAsClient :
default :
c .sendAlert (alertInternalError )
return errors .New ("tls: unknown Renegotiation value" )
}
c .handshakeMutex .Lock ()
defer c .handshakeMutex .Unlock ()
c .isHandshakeComplete .Store (false )
if err = c .BuildHandshakeState (); err != nil {
return err
}
if c .handshakeErr = c .clientHandshake (context .Background ()); c .handshakeErr == nil {
c .handshakes ++
}
return c .handshakeErr
}
func (c *UConn ) handlePostHandshakeMessage () error {
if c .vers != VersionTLS13 {
return c .handleRenegotiation ()
}
msg , err := c .readHandshake (nil )
if err != nil {
return err
}
c .retryCount ++
if c .retryCount > maxUselessRecords {
c .sendAlert (alertUnexpectedMessage )
return c .in .setErrorLocked (errors .New ("tls: too many non-advancing records" ))
}
switch msg := msg .(type ) {
case *newSessionTicketMsgTLS13 :
return c .handleNewSessionTicket (msg )
case *keyUpdateMsg :
return c .handleKeyUpdate (msg )
}
c .sendAlert (alertUnexpectedMessage )
return fmt .Errorf ("tls: received unexpected handshake message of type %T" , msg )
}
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 .