utls/examples/tls-resumption/main.go
3andne 75eb8e9e80
feat: add an option to skip resumption on nil ext & update examples (#239)
* feat: add an option to skip resumption on nil ext
feat: update examples

* fix: clone unit test
2023-10-05 12:47:24 -06:00

197 lines
5.7 KiB
Go

package main
import (
"fmt"
"net"
"strings"
"time"
tls "github.com/refraction-networking/utls"
)
type ClientSessionCache struct {
sessionKeyMap map[string]*tls.ClientSessionState
}
func NewClientSessionCache() tls.ClientSessionCache {
return &ClientSessionCache{
sessionKeyMap: make(map[string]*tls.ClientSessionState),
}
}
func (csc *ClientSessionCache) Get(sessionKey string) (session *tls.ClientSessionState, ok bool) {
if session, ok = csc.sessionKeyMap[sessionKey]; ok {
fmt.Printf("Getting session for %s\n", sessionKey)
return session, true
}
fmt.Printf("Missing session for %s\n", sessionKey)
return nil, false
}
func (csc *ClientSessionCache) Put(sessionKey string, cs *tls.ClientSessionState) {
if cs == nil {
fmt.Printf("Deleting session for %s\n", sessionKey)
delete(csc.sessionKeyMap, sessionKey)
} else {
fmt.Printf("Putting session for %s\n", sessionKey)
csc.sessionKeyMap[sessionKey] = cs
}
}
type ResumptionType int
const (
noResumption ResumptionType = 0
pskResumption ResumptionType = 1
ticketResumption ResumptionType = 2
)
func runResumptionCheck(helloID tls.ClientHelloID, getCustomSpec func() *tls.ClientHelloSpec, expectResumption ResumptionType, serverAddr string, retry int, verbose bool) {
fmt.Printf("checking: hello [%s], expectResumption [%v], serverAddr [%s]\n", helloID.Client, expectResumption, serverAddr)
csc := NewClientSessionCache()
tcpConn, err := net.Dial("tcp", serverAddr)
if err != nil {
panic(err)
}
// Everything below this line is brought to you by uTLS API, enjoy!
// use chs
tlsConn := tls.UClient(tcpConn, &tls.Config{
ServerName: strings.Split(serverAddr, ":")[0],
// NextProtos: []string{"h2", "http/1.1"},
ClientSessionCache: csc, // set this so session tickets will be saved
OmitEmptyPsk: true,
}, helloID)
if getCustomSpec != nil {
tlsConn.ApplyPreset(getCustomSpec())
}
// HS
err = tlsConn.Handshake()
if err != nil {
panic(err)
}
var tlsVer uint16
if tlsConn.ConnectionState().HandshakeComplete {
tlsVer = tlsConn.ConnectionState().Version
if verbose {
fmt.Println("Handshake complete")
fmt.Printf("TLS Version: %04x\n", tlsVer)
}
if tlsVer == tls.VersionTLS13 {
if verbose {
fmt.Printf("Expecting PSK resumption\n")
}
} else if tlsVer == tls.VersionTLS12 {
if verbose {
fmt.Printf("Expecting session ticket resumption\n")
}
} else {
panic("Don't try resumption on old TLS versions")
}
if tlsConn.HandshakeState.State13.UsingPSK {
panic("unintended using of PSK happened...")
} else if tlsConn.DidTls12Resume() {
panic("unintended using of session ticket happened...")
} else {
if verbose {
fmt.Println("First connection, no PSK/session ticket to use.")
}
}
tlsConn.SetReadDeadline(time.Now().Add(1 * time.Second))
tlsConn.Read(make([]byte, 1024)) // trigger a read so NewSessionTicket gets handled
}
tlsConn.Close()
resumption := noResumption
for i := 0; i < retry; i++ {
tcpConnPSK, err := net.Dial("tcp", serverAddr)
if err != nil {
panic(err)
}
tlsConnPSK := tls.UClient(tcpConnPSK, &tls.Config{
ServerName: strings.Split(serverAddr, ":")[0],
ClientSessionCache: csc,
OmitEmptyPsk: true,
}, helloID)
if getCustomSpec != nil {
tlsConnPSK.ApplyPreset(getCustomSpec())
}
// HS
err = tlsConnPSK.Handshake()
if verbose {
fmt.Printf("tlsConnPSK.HandshakeState.Hello.Raw %v\n", tlsConnPSK.HandshakeState.Hello.Raw)
fmt.Printf("tlsConnPSK.HandshakeState.Hello.PskIdentities: %v\n", tlsConnPSK.HandshakeState.Hello.PskIdentities)
}
if err != nil {
panic(err)
}
if tlsConnPSK.ConnectionState().HandshakeComplete {
if verbose {
fmt.Println("Handshake complete")
}
newVer := tlsConnPSK.ConnectionState().Version
if verbose {
fmt.Printf("TLS Version: %04x\n", newVer)
}
if newVer != tlsVer {
panic("Tls version changed unexpectedly on the second connection")
}
if tlsVer == tls.VersionTLS13 && tlsConnPSK.HandshakeState.State13.UsingPSK {
fmt.Println("[PSK used]")
resumption = pskResumption
break
} else if tlsVer == tls.VersionTLS12 && tlsConnPSK.DidTls12Resume() {
fmt.Println("[session ticket used]")
resumption = ticketResumption
break
}
}
time.Sleep(700 * time.Millisecond)
}
if resumption != expectResumption {
panic(fmt.Sprintf("Expecting resumption type: %v, actual %v; session, server %s, helloID: %s", expectResumption, resumption, serverAddr, helloID.Client))
} else {
fmt.Println("[expected]")
}
}
func main() {
tls13Url := "www.microsoft.com:443"
tls12Url1 := "spocs.getpocket.com:443"
tls12Url2 := "marketplace.visualstudio.com:443"
runResumptionCheck(tls.HelloChrome_100, nil, noResumption, tls13Url, 3, false) // no-resumption + utls
func() {
defer func() {
if err := recover(); err == nil {
panic("must throw")
}
}()
runResumptionCheck(tls.HelloCustom, func() *tls.ClientHelloSpec {
spec, _ := tls.UTLSIdToSpec(tls.HelloChrome_100)
return &spec
}, noResumption, tls13Url, 3, false) // no-resumption + utls custom + no psk extension
}()
runResumptionCheck(tls.HelloChrome_100_PSK, nil, pskResumption, tls13Url, 1, false) // psk + utls
runResumptionCheck(tls.HelloGolang, nil, pskResumption, tls13Url, 1, false) // psk + crypto/tls
runResumptionCheck(tls.HelloChrome_100_PSK, nil, ticketResumption, tls12Url1, 10, false) // session ticket + utls
runResumptionCheck(tls.HelloGolang, nil, ticketResumption, tls12Url1, 10, false) // session ticket + crypto/tls
runResumptionCheck(tls.HelloChrome_100_PSK, nil, ticketResumption, tls12Url2, 10, false) // session ticket + utls
runResumptionCheck(tls.HelloGolang, nil, ticketResumption, tls12Url2, 10, false) // session ticket + crypto/tls
}