-
Notifications
You must be signed in to change notification settings - Fork 18.3k
Description
By default a Go TLS server does not offer any Forward Secrecy in respect to the Session Ticket key, as that is fixed for the lifetime of the server. (And Session Tickets in 1.2 are awful: sent before the ChangeCipherSpec on every connection, with the ephemeral key material of the current connection.) Moreover, there is no way to abstract away the keys a-la-crypto.Signer
. Finally, there are already too many ways to specify keys. This proposal tries to address all these concerns.
The main interface would be SessionTicketWrapper
:
// A SessionTicketWrapper provides a way to securely incapsulate
// session state for storage on the client. All methods are safe for
// concurrent use.
type SessionTicketWrapper interface {
// Wrap returns a session ticket value that can be later passed to Unwrap
// 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.
Wrap(cs *ConnectionState, content []byte) (ticket []byte, err error)
// Unwrap returns a session ticket contents. The ticket can't be safely
// assumed to have been generated by Wrap.
// If unable to unwrap the ticket, the connection will proceed with a
// complete handshake.
Unwrap(chi *ClientHelloInfo, ticket []byte) (content []byte, success bool)
// Clone returns a new SessionTicketWrapper to be used in a
// new config. It can be either a copy or a reference to the
// existing one.
Clone() SessionTicketWrapper
}
A simple tls.Config
attribute would be the main hook.
type Config struct {
// SessionTicketWrapper, if not nil, is used to wrap and unwrap
// session tickets, instead of SessionTicketKey.
SessionTicketWrapper SessionTicketWrapper
}
Three standard implementations would be offered, providing behind the scenes all current behaviors and the new default.
// SessionTicketWrapperFixed provides a SessionTicketWrapper using
// a fixed encryption key.
type SessionTicketWrapperFixed struct {}
SessionTicketWrapperFixed
is instantiated under the hood when Config.SessionTicketKey
is set.
// SessionTicketWrapperManual provides a SessionTicketWrapper using
// a set of encryptions keys. The first one is used to encrypt new tickets,
// and the others are tried to decrypt existing ones. Keys are rotated manually
// by calling the SetSessionTicketKeys method.
type SessionTicketWrapperManual struct {}
SessionTicketWrapperManual
is instantiated under the hood when Config.SetSessionTicketKeys
is called.
// SessionTicketWrapperRotating provides a SessionTicketWrapper using
// a set of random encryption keys that are rotated automatically.
// The latest key is used to encrypt new tickets and older ones are tried
// for decryption of existing ones. Connections gain forward secrecy and
// tickets stop working after RotateEvery * MaxKeys elapses.
type SessionTicketWrapperRotating struct {
// RotateEvery is the period after which a new key is generated.
// If zero, it defaults to 3 hours.
RotateEvery time.Duration
// MaxKeys is the maximum number of keys that will be kept in
// memory to attempt decryption. If zero, it defaults to 8.
MaxKeys int
}
SessionTicketWrapperRotating
would be the default for a server that does not set any property, making the default have a 24-hours FS/resumption cliff.
On Clone
and GetConfigForClient
, SessionTicketWrapper.Clone
is called. The Fixed
and Manual
implementations make deep copies, so that operations on the new ones don't affect the original, maintaining current behavior.
By very strict reading, there might be a Go 1 violation: a server that starts without SessionTicketKey
, does a handshake, then copies the SessionTicketKey
expecting it to be the fixed key will fail. IMHO it's not a significant breakage and worth it for FS.
/cc @agl
Supersedes #17432