263 lines
7.7 KiB
Go
263 lines
7.7 KiB
Go
|
package mint
|
||
|
|
||
|
import (
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
// Marker interface for actions that an implementation should take based on
|
||
|
// state transitions.
|
||
|
type HandshakeAction interface{}
|
||
|
|
||
|
type QueueHandshakeMessage struct {
|
||
|
Message *HandshakeMessage
|
||
|
}
|
||
|
|
||
|
type SendQueuedHandshake struct{}
|
||
|
|
||
|
type SendEarlyData struct{}
|
||
|
|
||
|
type ReadEarlyData struct{}
|
||
|
|
||
|
type ReadPastEarlyData struct{}
|
||
|
|
||
|
type RekeyIn struct {
|
||
|
epoch Epoch
|
||
|
KeySet keySet
|
||
|
}
|
||
|
|
||
|
type RekeyOut struct {
|
||
|
epoch Epoch
|
||
|
KeySet keySet
|
||
|
}
|
||
|
|
||
|
type StorePSK struct {
|
||
|
PSK PreSharedKey
|
||
|
}
|
||
|
|
||
|
type HandshakeState interface {
|
||
|
Next(handshakeMessageReader) (HandshakeState, []HandshakeAction, Alert)
|
||
|
State() State
|
||
|
}
|
||
|
|
||
|
type AppExtensionHandler interface {
|
||
|
Send(hs HandshakeType, el *ExtensionList) error
|
||
|
Receive(hs HandshakeType, el *ExtensionList) error
|
||
|
}
|
||
|
|
||
|
// Capabilities objects represent the capabilities of a TLS client or server,
|
||
|
// as an input to TLS negotiation
|
||
|
type Capabilities struct {
|
||
|
// For both client and server
|
||
|
CipherSuites []CipherSuite
|
||
|
Groups []NamedGroup
|
||
|
SignatureSchemes []SignatureScheme
|
||
|
PSKs PreSharedKeyCache
|
||
|
Certificates []*Certificate
|
||
|
AuthCertificate func(chain []CertificateEntry) error
|
||
|
ExtensionHandler AppExtensionHandler
|
||
|
UseDTLS bool
|
||
|
// For client
|
||
|
PSKModes []PSKKeyExchangeMode
|
||
|
|
||
|
// For server
|
||
|
NextProtos []string
|
||
|
AllowEarlyData bool
|
||
|
RequireCookie bool
|
||
|
CookieProtector CookieProtector
|
||
|
CookieHandler CookieHandler
|
||
|
RequireClientAuth bool
|
||
|
}
|
||
|
|
||
|
// ConnectionOptions objects represent per-connection settings for a client
|
||
|
// initiating a connection
|
||
|
type ConnectionOptions struct {
|
||
|
ServerName string
|
||
|
NextProtos []string
|
||
|
EarlyData []byte
|
||
|
}
|
||
|
|
||
|
// ConnectionParameters objects represent the parameters negotiated for a
|
||
|
// connection.
|
||
|
type ConnectionParameters struct {
|
||
|
UsingPSK bool
|
||
|
UsingDH bool
|
||
|
ClientSendingEarlyData bool
|
||
|
UsingEarlyData bool
|
||
|
UsingClientAuth bool
|
||
|
|
||
|
CipherSuite CipherSuite
|
||
|
ServerName string
|
||
|
NextProto string
|
||
|
}
|
||
|
|
||
|
// Working state for the handshake.
|
||
|
type HandshakeContext struct {
|
||
|
hIn, hOut *HandshakeLayer
|
||
|
}
|
||
|
|
||
|
func (hc *HandshakeContext) SetVersion(version uint16) {
|
||
|
if hc.hIn.conn != nil {
|
||
|
hc.hIn.conn.SetVersion(version)
|
||
|
}
|
||
|
if hc.hOut.conn != nil {
|
||
|
hc.hOut.conn.SetVersion(version)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// StateConnected is symmetric between client and server
|
||
|
type StateConnected struct {
|
||
|
Params ConnectionParameters
|
||
|
hsCtx HandshakeContext
|
||
|
isClient bool
|
||
|
cryptoParams CipherSuiteParams
|
||
|
resumptionSecret []byte
|
||
|
clientTrafficSecret []byte
|
||
|
serverTrafficSecret []byte
|
||
|
exporterSecret []byte
|
||
|
}
|
||
|
|
||
|
var _ HandshakeState = &StateConnected{}
|
||
|
|
||
|
func (state StateConnected) State() State {
|
||
|
if state.isClient {
|
||
|
return StateClientConnected
|
||
|
}
|
||
|
return StateServerConnected
|
||
|
}
|
||
|
|
||
|
func (state *StateConnected) KeyUpdate(request KeyUpdateRequest) ([]HandshakeAction, Alert) {
|
||
|
var trafficKeys keySet
|
||
|
if state.isClient {
|
||
|
state.clientTrafficSecret = HkdfExpandLabel(state.cryptoParams.Hash, state.clientTrafficSecret,
|
||
|
labelClientApplicationTrafficSecret, []byte{}, state.cryptoParams.Hash.Size())
|
||
|
trafficKeys = makeTrafficKeys(state.cryptoParams, state.clientTrafficSecret)
|
||
|
} else {
|
||
|
state.serverTrafficSecret = HkdfExpandLabel(state.cryptoParams.Hash, state.serverTrafficSecret,
|
||
|
labelServerApplicationTrafficSecret, []byte{}, state.cryptoParams.Hash.Size())
|
||
|
trafficKeys = makeTrafficKeys(state.cryptoParams, state.serverTrafficSecret)
|
||
|
}
|
||
|
|
||
|
kum, err := state.hsCtx.hOut.HandshakeMessageFromBody(&KeyUpdateBody{KeyUpdateRequest: request})
|
||
|
if err != nil {
|
||
|
logf(logTypeHandshake, "[StateConnected] Error marshaling key update message: %v", err)
|
||
|
return nil, AlertInternalError
|
||
|
}
|
||
|
|
||
|
toSend := []HandshakeAction{
|
||
|
QueueHandshakeMessage{kum},
|
||
|
SendQueuedHandshake{},
|
||
|
RekeyOut{epoch: EpochUpdate, KeySet: trafficKeys},
|
||
|
}
|
||
|
return toSend, AlertNoAlert
|
||
|
}
|
||
|
|
||
|
func (state *StateConnected) NewSessionTicket(length int, lifetime, earlyDataLifetime uint32) ([]HandshakeAction, Alert) {
|
||
|
tkt, err := NewSessionTicket(length, lifetime)
|
||
|
if err != nil {
|
||
|
logf(logTypeHandshake, "[StateConnected] Error generating NewSessionTicket: %v", err)
|
||
|
return nil, AlertInternalError
|
||
|
}
|
||
|
|
||
|
err = tkt.Extensions.Add(&TicketEarlyDataInfoExtension{earlyDataLifetime})
|
||
|
if err != nil {
|
||
|
logf(logTypeHandshake, "[StateConnected] Error adding extension to NewSessionTicket: %v", err)
|
||
|
return nil, AlertInternalError
|
||
|
}
|
||
|
|
||
|
resumptionKey := HkdfExpandLabel(state.cryptoParams.Hash, state.resumptionSecret,
|
||
|
labelResumption, tkt.TicketNonce, state.cryptoParams.Hash.Size())
|
||
|
|
||
|
newPSK := PreSharedKey{
|
||
|
CipherSuite: state.cryptoParams.Suite,
|
||
|
IsResumption: true,
|
||
|
Identity: tkt.Ticket,
|
||
|
Key: resumptionKey,
|
||
|
NextProto: state.Params.NextProto,
|
||
|
ReceivedAt: time.Now(),
|
||
|
ExpiresAt: time.Now().Add(time.Duration(tkt.TicketLifetime) * time.Second),
|
||
|
TicketAgeAdd: tkt.TicketAgeAdd,
|
||
|
}
|
||
|
|
||
|
tktm, err := state.hsCtx.hOut.HandshakeMessageFromBody(tkt)
|
||
|
if err != nil {
|
||
|
logf(logTypeHandshake, "[StateConnected] Error marshaling NewSessionTicket: %v", err)
|
||
|
return nil, AlertInternalError
|
||
|
}
|
||
|
|
||
|
toSend := []HandshakeAction{
|
||
|
StorePSK{newPSK},
|
||
|
QueueHandshakeMessage{tktm},
|
||
|
SendQueuedHandshake{},
|
||
|
}
|
||
|
return toSend, AlertNoAlert
|
||
|
}
|
||
|
|
||
|
// Next does nothing for this state.
|
||
|
func (state StateConnected) Next(hr handshakeMessageReader) (HandshakeState, []HandshakeAction, Alert) {
|
||
|
return state, nil, AlertNoAlert
|
||
|
}
|
||
|
|
||
|
func (state StateConnected) ProcessMessage(hm *HandshakeMessage) (HandshakeState, []HandshakeAction, Alert) {
|
||
|
if hm == nil {
|
||
|
logf(logTypeHandshake, "[StateConnected] Unexpected message")
|
||
|
return nil, nil, AlertUnexpectedMessage
|
||
|
}
|
||
|
|
||
|
bodyGeneric, err := hm.ToBody()
|
||
|
if err != nil {
|
||
|
logf(logTypeHandshake, "[StateConnected] Error decoding message: %v", err)
|
||
|
return nil, nil, AlertDecodeError
|
||
|
}
|
||
|
|
||
|
switch body := bodyGeneric.(type) {
|
||
|
case *KeyUpdateBody:
|
||
|
var trafficKeys keySet
|
||
|
if !state.isClient {
|
||
|
state.clientTrafficSecret = HkdfExpandLabel(state.cryptoParams.Hash, state.clientTrafficSecret,
|
||
|
labelClientApplicationTrafficSecret, []byte{}, state.cryptoParams.Hash.Size())
|
||
|
trafficKeys = makeTrafficKeys(state.cryptoParams, state.clientTrafficSecret)
|
||
|
} else {
|
||
|
state.serverTrafficSecret = HkdfExpandLabel(state.cryptoParams.Hash, state.serverTrafficSecret,
|
||
|
labelServerApplicationTrafficSecret, []byte{}, state.cryptoParams.Hash.Size())
|
||
|
trafficKeys = makeTrafficKeys(state.cryptoParams, state.serverTrafficSecret)
|
||
|
}
|
||
|
|
||
|
toSend := []HandshakeAction{RekeyIn{epoch: EpochUpdate, KeySet: trafficKeys}}
|
||
|
|
||
|
// If requested, roll outbound keys and send a KeyUpdate
|
||
|
if body.KeyUpdateRequest == KeyUpdateRequested {
|
||
|
logf(logTypeHandshake, "Received key update, update requested", body.KeyUpdateRequest)
|
||
|
moreToSend, alert := state.KeyUpdate(KeyUpdateNotRequested)
|
||
|
if alert != AlertNoAlert {
|
||
|
return nil, nil, alert
|
||
|
}
|
||
|
toSend = append(toSend, moreToSend...)
|
||
|
}
|
||
|
return state, toSend, AlertNoAlert
|
||
|
case *NewSessionTicketBody:
|
||
|
// XXX: Allow NewSessionTicket in both directions?
|
||
|
if !state.isClient {
|
||
|
return nil, nil, AlertUnexpectedMessage
|
||
|
}
|
||
|
|
||
|
resumptionKey := HkdfExpandLabel(state.cryptoParams.Hash, state.resumptionSecret,
|
||
|
labelResumption, body.TicketNonce, state.cryptoParams.Hash.Size())
|
||
|
psk := PreSharedKey{
|
||
|
CipherSuite: state.cryptoParams.Suite,
|
||
|
IsResumption: true,
|
||
|
Identity: body.Ticket,
|
||
|
Key: resumptionKey,
|
||
|
NextProto: state.Params.NextProto,
|
||
|
ReceivedAt: time.Now(),
|
||
|
ExpiresAt: time.Now().Add(time.Duration(body.TicketLifetime) * time.Second),
|
||
|
TicketAgeAdd: body.TicketAgeAdd,
|
||
|
}
|
||
|
|
||
|
toSend := []HandshakeAction{StorePSK{psk}}
|
||
|
return state, toSend, AlertNoAlert
|
||
|
}
|
||
|
|
||
|
logf(logTypeHandshake, "[StateConnected] Unexpected message type %v", hm.msgType)
|
||
|
return nil, nil, AlertUnexpectedMessage
|
||
|
}
|