@@ -191,7 +191,7 @@ func (hs *serverHandshakeState) readClientFinished13() error { | |||||
// happens, even if the Read call might do more work. | // happens, even if the Read call might do more work. | ||||
c.confirmMutex.Unlock() | c.confirmMutex.Unlock() | ||||
return hs.sendSessionTicket13() | |||||
return hs.sendSessionTicket13() // TODO: do in a goroutine | |||||
} | } | ||||
func (hs *serverHandshakeState) sendCertificate13() error { | func (hs *serverHandshakeState) sendCertificate13() error { | ||||
@@ -460,12 +460,20 @@ func (hs *serverHandshakeState) checkPSK() (earlySecret []byte, alert alert) { | |||||
hashSize := hash.Size() | hashSize := hash.Size() | ||||
for i := range hs.clientHello.psks { | for i := range hs.clientHello.psks { | ||||
sessionTicket := append([]uint8{}, hs.clientHello.psks[i].identity...) | sessionTicket := append([]uint8{}, hs.clientHello.psks[i].identity...) | ||||
serializedTicket, _ := hs.c.decryptTicket(sessionTicket) | |||||
if serializedTicket == nil { | |||||
continue | |||||
if hs.c.config.SessionTicketSealer != nil { | |||||
var ok bool | |||||
sessionTicket, ok = hs.c.config.SessionTicketSealer.Unseal(hs.clientHelloInfo(), sessionTicket) | |||||
if !ok { | |||||
continue | |||||
} | |||||
} else { | |||||
sessionTicket, _ = hs.c.decryptTicket(sessionTicket) | |||||
if sessionTicket == nil { | |||||
continue | |||||
} | |||||
} | } | ||||
s := &sessionState13{} | s := &sessionState13{} | ||||
if s.unmarshal(serializedTicket) != alertSuccess { | |||||
if s.unmarshal(sessionTicket) != alertSuccess { | |||||
continue | continue | ||||
} | } | ||||
if s.vers != hs.c.vers { | if s.vers != hs.c.vers { | ||||
@@ -565,11 +573,21 @@ func (hs *serverHandshakeState) sendSessionTicket13() error { | |||||
} | } | ||||
for i := 0; i < numSessionTickets; i++ { | for i := 0; i < numSessionTickets; i++ { | ||||
ticket, err := c.encryptTicket(sessionState.marshal()) | |||||
ticket := sessionState.marshal() | |||||
var err error | |||||
if c.config.SessionTicketSealer != nil { | |||||
cs := c.ConnectionState() | |||||
ticket, err = c.config.SessionTicketSealer.Seal(&cs, ticket) | |||||
} else { | |||||
ticket, err = c.encryptTicket(ticket) | |||||
} | |||||
if err != nil { | if err != nil { | ||||
c.sendAlert(alertInternalError) | c.sendAlert(alertInternalError) | ||||
return err | return err | ||||
} | } | ||||
if ticket == nil { | |||||
continue | |||||
} | |||||
ticketMsg := &newSessionTicketMsg13{ | ticketMsg := &newSessionTicketMsg13{ | ||||
lifetime: 24 * 3600, // TODO(filippo) | lifetime: 24 * 3600, // TODO(filippo) | ||||
maxEarlyDataLength: c.config.Max0RTTDataSize, | maxEarlyDataLength: c.config.Max0RTTDataSize, | ||||
@@ -592,6 +592,10 @@ type Config struct { | |||||
// See https://tools.ietf.org/html/draft-ietf-tls-tls13-18#section-2.3. | // See https://tools.ietf.org/html/draft-ietf-tls-tls13-18#section-2.3. | ||||
Accept0RTTData bool | Accept0RTTData bool | ||||
// SessionTicketSealer, if not nil, is used to wrap and unwrap | |||||
// session tickets, instead of SessionTicketKey. | |||||
SessionTicketSealer SessionTicketSealer | |||||
serverInitOnce sync.Once // guards calling (*Config).serverInit | serverInitOnce sync.Once // guards calling (*Config).serverInit | ||||
// mutex protects sessionTicketKeys. | // mutex protects sessionTicketKeys. | ||||
@@ -668,6 +672,7 @@ func (c *Config) Clone() *Config { | |||||
KeyLogWriter: c.KeyLogWriter, | KeyLogWriter: c.KeyLogWriter, | ||||
Accept0RTTData: c.Accept0RTTData, | Accept0RTTData: c.Accept0RTTData, | ||||
Max0RTTDataSize: c.Max0RTTDataSize, | Max0RTTDataSize: c.Max0RTTDataSize, | ||||
SessionTicketSealer: c.SessionTicketSealer, | |||||
sessionTicketKeys: sessionTicketKeys, | sessionTicketKeys: sessionTicketKeys, | ||||
} | } | ||||
} | } | ||||
@@ -676,7 +681,7 @@ func (c *Config) Clone() *Config { | |||||
// returned by a GetConfigForClient callback then the argument should be the | // returned by a GetConfigForClient callback then the argument should be the | ||||
// Config that was passed to Server, otherwise it should be nil. | // Config that was passed to Server, otherwise it should be nil. | ||||
func (c *Config) serverInit(originalConfig *Config) { | func (c *Config) serverInit(originalConfig *Config) { | ||||
if c.SessionTicketsDisabled || len(c.ticketKeys()) != 0 { | |||||
if c.SessionTicketsDisabled || len(c.ticketKeys()) != 0 || c.SessionTicketSealer != nil { | |||||
return | return | ||||
} | } | ||||
@@ -15,6 +15,23 @@ import ( | |||||
"io" | "io" | ||||
) | ) | ||||
// A SessionTicketSealer provides a way to securely encapsulate | |||||
// session state for storage on the client. All methods are safe for | |||||
// concurrent use. | |||||
type SessionTicketSealer interface { | |||||
// Seal returns a session ticket value that can be later passed to Unseal | |||||
// to recover the content, usually by encrypting it. The ticket will be sent | |||||
// to the client to be stored, and will be sent back in plaintext, so it can | |||||
// be read and modified by an attacker. | |||||
Seal(cs *ConnectionState, content []byte) (ticket []byte, err error) | |||||
// Unseal returns a session ticket contents. The ticket can't be safely | |||||
// assumed to have been generated by Seal. | |||||
// If unable to unseal the ticket, the connection will proceed with a | |||||
// complete handshake. | |||||
Unseal(chi *ClientHelloInfo, ticket []byte) (content []byte, success bool) | |||||
} | |||||
// sessionState contains the information that is serialized into a session | // sessionState contains the information that is serialized into a session | ||||
// ticket in order to later resume a connection. | // ticket in order to later resume a connection. | ||||
type sessionState struct { | type sessionState struct { | ||||
@@ -656,6 +656,8 @@ func TestCloneNonFuncFields(t *testing.T) { | |||||
f.Set(reflect.ValueOf(RenegotiateOnceAsClient)) | f.Set(reflect.ValueOf(RenegotiateOnceAsClient)) | ||||
case "Max0RTTDataSize": | case "Max0RTTDataSize": | ||||
f.Set(reflect.ValueOf(uint32(0))) | f.Set(reflect.ValueOf(uint32(0))) | ||||
case "SessionTicketSealer": | |||||
// TODO | |||||
default: | default: | ||||
t.Errorf("all fields must be accounted for, but saw unknown field %q", fn) | t.Errorf("all fields must be accounted for, but saw unknown field %q", fn) | ||||
} | } | ||||