forked from quic-go/quic-go
implement a ClientSessionCache that can save application data
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
run:
|
run:
|
||||||
skip-files:
|
skip-files:
|
||||||
|
- internal/handshake/client_session_state.go
|
||||||
- internal/handshake/unsafe_test.go
|
- internal/handshake/unsafe_test.go
|
||||||
|
|
||||||
linters-settings:
|
linters-settings:
|
||||||
|
|||||||
83
internal/handshake/client_session_cache.go
Normal file
83
internal/handshake/client_session_cache.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package handshake
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/asn1"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/marten-seemann/qtls"
|
||||||
|
)
|
||||||
|
|
||||||
|
type nonceField struct {
|
||||||
|
Nonce []byte
|
||||||
|
AppData []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientSessionCache struct {
|
||||||
|
tls.ClientSessionCache
|
||||||
|
|
||||||
|
getAppData func() []byte
|
||||||
|
setAppData func([]byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClientSessionCache(cache tls.ClientSessionCache, get func() []byte, set func([]byte)) *clientSessionCache {
|
||||||
|
return &clientSessionCache{
|
||||||
|
ClientSessionCache: cache,
|
||||||
|
getAppData: get,
|
||||||
|
setAppData: set,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ qtls.ClientSessionCache = &clientSessionCache{}
|
||||||
|
|
||||||
|
func (c *clientSessionCache) Get(sessionKey string) (*qtls.ClientSessionState, bool) {
|
||||||
|
sess, ok := c.ClientSessionCache.Get(sessionKey)
|
||||||
|
if sess == nil {
|
||||||
|
return nil, ok
|
||||||
|
}
|
||||||
|
// qtls.ClientSessionState is identical to the tls.ClientSessionState.
|
||||||
|
// In order to allow users of quic-go to use a tls.Config,
|
||||||
|
// we need this workaround to use the ClientSessionCache.
|
||||||
|
// In unsafe.go we check that the two structs are actually identical.
|
||||||
|
tlsSessBytes := (*[unsafe.Sizeof(*sess)]byte)(unsafe.Pointer(sess))[:]
|
||||||
|
var session clientSessionState
|
||||||
|
sessBytes := (*[unsafe.Sizeof(session)]byte)(unsafe.Pointer(&session))[:]
|
||||||
|
copy(sessBytes, tlsSessBytes)
|
||||||
|
var nf nonceField
|
||||||
|
if _, err := asn1.Unmarshal(session.nonce, &nf); err != nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
c.setAppData(nf.AppData)
|
||||||
|
session.nonce = nf.Nonce
|
||||||
|
var qtlsSession qtls.ClientSessionState
|
||||||
|
qtlsSessBytes := (*[unsafe.Sizeof(qtlsSession)]byte)(unsafe.Pointer(&qtlsSession))[:]
|
||||||
|
copy(qtlsSessBytes, sessBytes)
|
||||||
|
return &qtlsSession, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientSessionCache) Put(sessionKey string, cs *qtls.ClientSessionState) {
|
||||||
|
if cs == nil {
|
||||||
|
c.ClientSessionCache.Put(sessionKey, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// qtls.ClientSessionState is identical to the tls.ClientSessionState.
|
||||||
|
// In order to allow users of quic-go to use a tls.Config,
|
||||||
|
// we need this workaround to use the ClientSessionCache.
|
||||||
|
// In unsafe.go we check that the two structs are actually identical.
|
||||||
|
qtlsSessBytes := (*[unsafe.Sizeof(*cs)]byte)(unsafe.Pointer(cs))[:]
|
||||||
|
var session clientSessionState
|
||||||
|
sessBytes := (*[unsafe.Sizeof(session)]byte)(unsafe.Pointer(&session))[:]
|
||||||
|
copy(sessBytes, qtlsSessBytes)
|
||||||
|
nonce, err := asn1.Marshal(nonceField{
|
||||||
|
Nonce: session.nonce,
|
||||||
|
AppData: c.getAppData(),
|
||||||
|
})
|
||||||
|
if err != nil { // marshaling
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
session.nonce = nonce
|
||||||
|
var tlsSession tls.ClientSessionState
|
||||||
|
tlsSessBytes := (*[unsafe.Sizeof(tlsSession)]byte)(unsafe.Pointer(&tlsSession))[:]
|
||||||
|
copy(tlsSessBytes, sessBytes)
|
||||||
|
c.ClientSessionCache.Put(sessionKey, &tlsSession)
|
||||||
|
}
|
||||||
38
internal/handshake/client_session_cache_test.go
Normal file
38
internal/handshake/client_session_cache_test.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package handshake
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
|
||||||
|
"github.com/marten-seemann/qtls"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("ClientSessionCache", func() {
|
||||||
|
var (
|
||||||
|
csc *clientSessionCache
|
||||||
|
get, set chan []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
get = make(chan []byte, 100)
|
||||||
|
set = make(chan []byte, 100)
|
||||||
|
csc = newClientSessionCache(
|
||||||
|
tls.NewLRUClientSessionCache(100),
|
||||||
|
func() []byte { return <-get },
|
||||||
|
func(b []byte) { set <- b },
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("puts and gets", func() {
|
||||||
|
get <- []byte("foobar")
|
||||||
|
csc.Put("localhost", &qtls.ClientSessionState{})
|
||||||
|
Expect(set).To(BeEmpty())
|
||||||
|
|
||||||
|
state, ok := csc.Get("localhost")
|
||||||
|
Expect(ok).To(BeTrue())
|
||||||
|
Expect(state).ToNot(BeNil())
|
||||||
|
Expect(set).To(Receive(Equal([]byte("foobar"))))
|
||||||
|
})
|
||||||
|
})
|
||||||
23
internal/handshake/client_session_state.go
Normal file
23
internal/handshake/client_session_state.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package handshake
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// copied from crypto/tls
|
||||||
|
// Needs to be in a separate file so golangci-lint can skip it.
|
||||||
|
type clientSessionState struct {
|
||||||
|
sessionTicket []uint8 // Encrypted ticket used for session resumption with server
|
||||||
|
vers uint16 // SSL/TLS version negotiated for the session
|
||||||
|
cipherSuite uint16 // Ciphersuite negotiated for the session
|
||||||
|
masterSecret []byte // Full handshake MasterSecret, or TLS 1.3 resumption_master_secret
|
||||||
|
serverCertificates []*x509.Certificate // Certificate chain presented by the server
|
||||||
|
verifiedChains [][]*x509.Certificate // Certificate chains we built for verification
|
||||||
|
receivedAt time.Time // When the session ticket was received from the server
|
||||||
|
|
||||||
|
// TLS 1.3 fields.
|
||||||
|
nonce []byte // Ticket nonce sent by the server, to derive PSK
|
||||||
|
useBy time.Time // Expiration of the ticket lifetime as set by the server
|
||||||
|
ageAdd uint32 // Random obfuscation factor for sending the ticket age
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/marten-seemann/qtls"
|
"github.com/marten-seemann/qtls"
|
||||||
)
|
)
|
||||||
@@ -28,44 +27,6 @@ func (c *conn) SetReadDeadline(time.Time) error { return nil }
|
|||||||
func (c *conn) SetWriteDeadline(time.Time) error { return nil }
|
func (c *conn) SetWriteDeadline(time.Time) error { return nil }
|
||||||
func (c *conn) SetDeadline(time.Time) error { return nil }
|
func (c *conn) SetDeadline(time.Time) error { return nil }
|
||||||
|
|
||||||
type clientSessionCache struct {
|
|
||||||
tls.ClientSessionCache
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ qtls.ClientSessionCache = &clientSessionCache{}
|
|
||||||
|
|
||||||
func (c *clientSessionCache) Get(sessionKey string) (*qtls.ClientSessionState, bool) {
|
|
||||||
sess, ok := c.ClientSessionCache.Get(sessionKey)
|
|
||||||
if sess == nil {
|
|
||||||
return nil, ok
|
|
||||||
}
|
|
||||||
// qtls.ClientSessionState is identical to the tls.ClientSessionState.
|
|
||||||
// In order to allow users of quic-go to use a tls.Config,
|
|
||||||
// we need this workaround to use the ClientSessionCache.
|
|
||||||
// In unsafe.go we check that the two structs are actually identical.
|
|
||||||
usess := (*[unsafe.Sizeof(*sess)]byte)(unsafe.Pointer(sess))[:]
|
|
||||||
var session qtls.ClientSessionState
|
|
||||||
usession := (*[unsafe.Sizeof(session)]byte)(unsafe.Pointer(&session))[:]
|
|
||||||
copy(usession, usess)
|
|
||||||
return &session, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *clientSessionCache) Put(sessionKey string, cs *qtls.ClientSessionState) {
|
|
||||||
if cs == nil {
|
|
||||||
c.ClientSessionCache.Put(sessionKey, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// qtls.ClientSessionState is identical to the tls.ClientSessionState.
|
|
||||||
// In order to allow users of quic-go to use a tls.Config,
|
|
||||||
// we need this workaround to use the ClientSessionCache.
|
|
||||||
// In unsafe.go we check that the two structs are actually identical.
|
|
||||||
usess := (*[unsafe.Sizeof(*cs)]byte)(unsafe.Pointer(cs))[:]
|
|
||||||
var session tls.ClientSessionState
|
|
||||||
usession := (*[unsafe.Sizeof(session)]byte)(unsafe.Pointer(&session))[:]
|
|
||||||
copy(usession, usess)
|
|
||||||
c.ClientSessionCache.Put(sessionKey, &session)
|
|
||||||
}
|
|
||||||
|
|
||||||
func tlsConfigToQtlsConfig(
|
func tlsConfigToQtlsConfig(
|
||||||
c *tls.Config,
|
c *tls.Config,
|
||||||
recordLayer qtls.RecordLayer,
|
recordLayer qtls.RecordLayer,
|
||||||
@@ -103,7 +64,11 @@ func tlsConfigToQtlsConfig(
|
|||||||
}
|
}
|
||||||
var csc qtls.ClientSessionCache
|
var csc qtls.ClientSessionCache
|
||||||
if c.ClientSessionCache != nil {
|
if c.ClientSessionCache != nil {
|
||||||
csc = &clientSessionCache{c.ClientSessionCache}
|
csc = newClientSessionCache(
|
||||||
|
c.ClientSessionCache,
|
||||||
|
func() []byte { return nil },
|
||||||
|
func([]byte) {},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
conf := &qtls.Config{
|
conf := &qtls.Config{
|
||||||
Rand: c.Rand,
|
Rand: c.Rand,
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ func init() {
|
|||||||
if !structsEqual(&tls.ClientSessionState{}, &qtls.ClientSessionState{}) {
|
if !structsEqual(&tls.ClientSessionState{}, &qtls.ClientSessionState{}) {
|
||||||
panic("qtls.ClientSessionState not compatible with tls.ClientSessionState")
|
panic("qtls.ClientSessionState not compatible with tls.ClientSessionState")
|
||||||
}
|
}
|
||||||
|
if !structsEqual(&tls.ClientSessionState{}, &clientSessionState{}) {
|
||||||
|
panic("clientSessionState not compatible with tls.ClientSessionState")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func structsEqual(a, b interface{}) bool {
|
func structsEqual(a, b interface{}) bool {
|
||||||
|
|||||||
Reference in New Issue
Block a user