Skip to content

proposal: crypto/tls: SessionTicketWrapper and Forward Secrecy by default #19199

@FiloSottile

Description

@FiloSottile

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions