36533f7c3f
Fixes for new geth version
585 lines
17 KiB
Go
585 lines
17 KiB
Go
package handshake
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"sync"
|
|
"unsafe"
|
|
|
|
"github.com/lucas-clemente/quic-go/internal/protocol"
|
|
"github.com/lucas-clemente/quic-go/internal/qerr"
|
|
"github.com/lucas-clemente/quic-go/internal/utils"
|
|
"github.com/marten-seemann/qtls"
|
|
)
|
|
|
|
// TLS unexpected_message alert
|
|
const alertUnexpectedMessage uint8 = 10
|
|
|
|
type messageType uint8
|
|
|
|
// TLS handshake message types.
|
|
const (
|
|
typeClientHello messageType = 1
|
|
typeServerHello messageType = 2
|
|
typeNewSessionTicket messageType = 4
|
|
typeEncryptedExtensions messageType = 8
|
|
typeCertificate messageType = 11
|
|
typeCertificateRequest messageType = 13
|
|
typeCertificateVerify messageType = 15
|
|
typeFinished messageType = 20
|
|
)
|
|
|
|
func (m messageType) String() string {
|
|
switch m {
|
|
case typeClientHello:
|
|
return "ClientHello"
|
|
case typeServerHello:
|
|
return "ServerHello"
|
|
case typeNewSessionTicket:
|
|
return "NewSessionTicket"
|
|
case typeEncryptedExtensions:
|
|
return "EncryptedExtensions"
|
|
case typeCertificate:
|
|
return "Certificate"
|
|
case typeCertificateRequest:
|
|
return "CertificateRequest"
|
|
case typeCertificateVerify:
|
|
return "CertificateVerify"
|
|
case typeFinished:
|
|
return "Finished"
|
|
default:
|
|
return fmt.Sprintf("unknown message type: %d", m)
|
|
}
|
|
}
|
|
|
|
// ErrOpenerNotYetAvailable is returned when an opener is requested for an encryption level,
|
|
// but the corresponding opener has not yet been initialized
|
|
// This can happen when packets arrive out of order.
|
|
var ErrOpenerNotYetAvailable = errors.New("CryptoSetup: opener at this encryption level not yet available")
|
|
|
|
type cryptoSetup struct {
|
|
tlsConf *qtls.Config
|
|
conn *qtls.Conn
|
|
|
|
messageChan chan []byte
|
|
|
|
paramsChan <-chan []byte
|
|
handleParamsCallback func([]byte)
|
|
|
|
alertChan chan uint8
|
|
// HandleData() sends errors on the messageErrChan
|
|
messageErrChan chan error
|
|
// handshakeDone is closed as soon as the go routine running qtls.Handshake() returns
|
|
handshakeDone chan struct{}
|
|
// is closed when Close() is called
|
|
closeChan chan struct{}
|
|
|
|
clientHelloWritten bool
|
|
clientHelloWrittenChan chan struct{}
|
|
|
|
receivedWriteKey chan struct{}
|
|
receivedReadKey chan struct{}
|
|
// WriteRecord does a non-blocking send on this channel.
|
|
// This way, handleMessage can see if qtls tries to write a message.
|
|
// This is necessary:
|
|
// for servers: to see if a HelloRetryRequest should be sent in response to a ClientHello
|
|
// for clients: to see if a ServerHello is a HelloRetryRequest
|
|
writeRecord chan struct{}
|
|
|
|
logger utils.Logger
|
|
|
|
perspective protocol.Perspective
|
|
|
|
mutex sync.Mutex // protects all members below
|
|
|
|
readEncLevel protocol.EncryptionLevel
|
|
writeEncLevel protocol.EncryptionLevel
|
|
|
|
initialStream io.Writer
|
|
initialOpener Opener
|
|
initialSealer Sealer
|
|
|
|
handshakeStream io.Writer
|
|
handshakeOpener Opener
|
|
handshakeSealer Sealer
|
|
|
|
oneRTTStream io.Writer
|
|
opener Opener
|
|
sealer Sealer
|
|
}
|
|
|
|
var _ qtls.RecordLayer = &cryptoSetup{}
|
|
var _ CryptoSetup = &cryptoSetup{}
|
|
|
|
// NewCryptoSetupClient creates a new crypto setup for the client
|
|
func NewCryptoSetupClient(
|
|
initialStream io.Writer,
|
|
handshakeStream io.Writer,
|
|
oneRTTStream io.Writer,
|
|
connID protocol.ConnectionID,
|
|
remoteAddr net.Addr,
|
|
tp *TransportParameters,
|
|
handleParams func([]byte),
|
|
tlsConf *tls.Config,
|
|
logger utils.Logger,
|
|
) (CryptoSetup, <-chan struct{} /* ClientHello written */, error) {
|
|
cs, clientHelloWritten, err := newCryptoSetup(
|
|
initialStream,
|
|
handshakeStream,
|
|
oneRTTStream,
|
|
connID,
|
|
tp,
|
|
handleParams,
|
|
tlsConf,
|
|
logger,
|
|
protocol.PerspectiveClient,
|
|
)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
cs.conn = qtls.Client(newConn(remoteAddr), cs.tlsConf)
|
|
return cs, clientHelloWritten, nil
|
|
}
|
|
|
|
// NewCryptoSetupServer creates a new crypto setup for the server
|
|
func NewCryptoSetupServer(
|
|
initialStream io.Writer,
|
|
handshakeStream io.Writer,
|
|
oneRTTStream io.Writer,
|
|
connID protocol.ConnectionID,
|
|
remoteAddr net.Addr,
|
|
tp *TransportParameters,
|
|
handleParams func([]byte),
|
|
tlsConf *tls.Config,
|
|
logger utils.Logger,
|
|
) (CryptoSetup, error) {
|
|
cs, _, err := newCryptoSetup(
|
|
initialStream,
|
|
handshakeStream,
|
|
oneRTTStream,
|
|
connID,
|
|
tp,
|
|
handleParams,
|
|
tlsConf,
|
|
logger,
|
|
protocol.PerspectiveServer,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cs.conn = qtls.Server(newConn(remoteAddr), cs.tlsConf)
|
|
return cs, nil
|
|
}
|
|
|
|
func newCryptoSetup(
|
|
initialStream io.Writer,
|
|
handshakeStream io.Writer,
|
|
oneRTTStream io.Writer,
|
|
connID protocol.ConnectionID,
|
|
tp *TransportParameters,
|
|
handleParams func([]byte),
|
|
tlsConf *tls.Config,
|
|
logger utils.Logger,
|
|
perspective protocol.Perspective,
|
|
) (*cryptoSetup, <-chan struct{} /* ClientHello written */, error) {
|
|
initialSealer, initialOpener, err := NewInitialAEAD(connID, perspective)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
extHandler := newExtensionHandler(tp.Marshal(), perspective)
|
|
cs := &cryptoSetup{
|
|
initialStream: initialStream,
|
|
initialSealer: initialSealer,
|
|
initialOpener: initialOpener,
|
|
handshakeStream: handshakeStream,
|
|
oneRTTStream: oneRTTStream,
|
|
readEncLevel: protocol.EncryptionInitial,
|
|
writeEncLevel: protocol.EncryptionInitial,
|
|
handleParamsCallback: handleParams,
|
|
paramsChan: extHandler.TransportParameters(),
|
|
logger: logger,
|
|
perspective: perspective,
|
|
handshakeDone: make(chan struct{}),
|
|
alertChan: make(chan uint8),
|
|
messageErrChan: make(chan error, 1),
|
|
clientHelloWrittenChan: make(chan struct{}),
|
|
messageChan: make(chan []byte, 100),
|
|
receivedReadKey: make(chan struct{}),
|
|
receivedWriteKey: make(chan struct{}),
|
|
writeRecord: make(chan struct{}, 1),
|
|
closeChan: make(chan struct{}),
|
|
}
|
|
qtlsConf := tlsConfigToQtlsConfig(tlsConf, cs, extHandler)
|
|
cs.tlsConf = qtlsConf
|
|
return cs, cs.clientHelloWrittenChan, nil
|
|
}
|
|
|
|
func (h *cryptoSetup) ChangeConnectionID(id protocol.ConnectionID) error {
|
|
initialSealer, initialOpener, err := NewInitialAEAD(id, h.perspective)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
h.initialSealer = initialSealer
|
|
h.initialOpener = initialOpener
|
|
return nil
|
|
}
|
|
|
|
func (h *cryptoSetup) RunHandshake() error {
|
|
// Handle errors that might occur when HandleData() is called.
|
|
handshakeComplete := make(chan struct{})
|
|
handshakeErrChan := make(chan error, 1)
|
|
go func() {
|
|
defer close(h.handshakeDone)
|
|
if err := h.conn.Handshake(); err != nil {
|
|
handshakeErrChan <- err
|
|
return
|
|
}
|
|
close(handshakeComplete)
|
|
}()
|
|
|
|
select {
|
|
case <-h.closeChan:
|
|
close(h.messageChan)
|
|
// wait until the Handshake() go routine has returned
|
|
return errors.New("Handshake aborted")
|
|
case <-handshakeComplete: // return when the handshake is done
|
|
return nil
|
|
case alert := <-h.alertChan:
|
|
err := <-handshakeErrChan
|
|
return qerr.CryptoError(alert, err.Error())
|
|
case err := <-h.messageErrChan:
|
|
// If the handshake errored because of an error that occurred during HandleData(),
|
|
// that error message will be more useful than the error message generated by Handshake().
|
|
// Close the message chan that qtls is receiving messages from.
|
|
// This will make qtls.Handshake() return.
|
|
// Thereby the go routine running qtls.Handshake() will return.
|
|
close(h.messageChan)
|
|
return err
|
|
}
|
|
}
|
|
|
|
func (h *cryptoSetup) Close() error {
|
|
close(h.closeChan)
|
|
// wait until qtls.Handshake() actually returned
|
|
<-h.handshakeDone
|
|
return nil
|
|
}
|
|
|
|
// handleMessage handles a TLS handshake message.
|
|
// It is called by the crypto streams when a new message is available.
|
|
// It returns if it is done with messages on the same encryption level.
|
|
func (h *cryptoSetup) HandleMessage(data []byte, encLevel protocol.EncryptionLevel) bool /* stream finished */ {
|
|
msgType := messageType(data[0])
|
|
h.logger.Debugf("Received %s message (%d bytes, encryption level: %s)", msgType, len(data), encLevel)
|
|
if err := h.checkEncryptionLevel(msgType, encLevel); err != nil {
|
|
h.messageErrChan <- err
|
|
return false
|
|
}
|
|
h.messageChan <- data
|
|
switch h.perspective {
|
|
case protocol.PerspectiveClient:
|
|
return h.handleMessageForClient(msgType)
|
|
case protocol.PerspectiveServer:
|
|
return h.handleMessageForServer(msgType)
|
|
default:
|
|
panic("")
|
|
}
|
|
}
|
|
|
|
func (h *cryptoSetup) checkEncryptionLevel(msgType messageType, encLevel protocol.EncryptionLevel) error {
|
|
var expected protocol.EncryptionLevel
|
|
switch msgType {
|
|
case typeClientHello,
|
|
typeServerHello:
|
|
expected = protocol.EncryptionInitial
|
|
case typeEncryptedExtensions,
|
|
typeCertificate,
|
|
typeCertificateRequest,
|
|
typeCertificateVerify,
|
|
typeFinished:
|
|
expected = protocol.EncryptionHandshake
|
|
case typeNewSessionTicket:
|
|
expected = protocol.Encryption1RTT
|
|
default:
|
|
return qerr.CryptoError(alertUnexpectedMessage, fmt.Sprintf("unexpected handshake message: %d", msgType))
|
|
}
|
|
if encLevel != expected {
|
|
return qerr.CryptoError(alertUnexpectedMessage, fmt.Sprintf("expected handshake message %s to have encryption level %s, has %s", msgType, expected, encLevel))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *cryptoSetup) handleMessageForServer(msgType messageType) bool {
|
|
switch msgType {
|
|
case typeClientHello:
|
|
select {
|
|
case <-h.writeRecord:
|
|
// If qtls sends a HelloRetryRequest, it will only write the record.
|
|
// If it accepts the ClientHello, it will first read the transport parameters.
|
|
h.logger.Debugf("Sending HelloRetryRequest")
|
|
return false
|
|
case data := <-h.paramsChan:
|
|
h.handleParamsCallback(data)
|
|
case <-h.handshakeDone:
|
|
return false
|
|
}
|
|
// get the handshake read key
|
|
select {
|
|
case <-h.receivedReadKey:
|
|
case <-h.handshakeDone:
|
|
return false
|
|
}
|
|
// get the handshake write key
|
|
select {
|
|
case <-h.receivedWriteKey:
|
|
case <-h.handshakeDone:
|
|
return false
|
|
}
|
|
// get the 1-RTT write key
|
|
select {
|
|
case <-h.receivedWriteKey:
|
|
case <-h.handshakeDone:
|
|
return false
|
|
}
|
|
return true
|
|
case typeCertificate, typeCertificateVerify:
|
|
// nothing to do
|
|
return false
|
|
case typeFinished:
|
|
// get the 1-RTT read key
|
|
select {
|
|
case <-h.receivedReadKey:
|
|
case <-h.handshakeDone:
|
|
return false
|
|
}
|
|
return true
|
|
default:
|
|
h.messageErrChan <- qerr.CryptoError(alertUnexpectedMessage, fmt.Sprintf("unexpected handshake message: %d", msgType))
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (h *cryptoSetup) handleMessageForClient(msgType messageType) bool {
|
|
switch msgType {
|
|
case typeServerHello:
|
|
// get the handshake write key
|
|
select {
|
|
case <-h.writeRecord:
|
|
// If qtls writes in response to a ServerHello, this means that this ServerHello
|
|
// is a HelloRetryRequest.
|
|
// Otherwise, we'd just wait for the Certificate message.
|
|
h.logger.Debugf("ServerHello is a HelloRetryRequest")
|
|
return false
|
|
case <-h.receivedWriteKey:
|
|
case <-h.handshakeDone:
|
|
return false
|
|
}
|
|
// get the handshake read key
|
|
select {
|
|
case <-h.receivedReadKey:
|
|
case <-h.handshakeDone:
|
|
return false
|
|
}
|
|
return true
|
|
case typeEncryptedExtensions:
|
|
select {
|
|
case data := <-h.paramsChan:
|
|
h.handleParamsCallback(data)
|
|
case <-h.handshakeDone:
|
|
return false
|
|
}
|
|
return false
|
|
case typeCertificateRequest, typeCertificate, typeCertificateVerify:
|
|
// nothing to do
|
|
return false
|
|
case typeFinished:
|
|
// get the 1-RTT read key
|
|
select {
|
|
case <-h.receivedReadKey:
|
|
case <-h.handshakeDone:
|
|
return false
|
|
}
|
|
// get the handshake write key
|
|
select {
|
|
case <-h.receivedWriteKey:
|
|
case <-h.handshakeDone:
|
|
return false
|
|
}
|
|
return true
|
|
case typeNewSessionTicket:
|
|
<-h.handshakeDone // don't process session tickets before the handshake has completed
|
|
h.conn.HandlePostHandshakeMessage()
|
|
return false
|
|
default:
|
|
h.messageErrChan <- qerr.CryptoError(alertUnexpectedMessage, fmt.Sprintf("unexpected handshake message: %d", msgType))
|
|
return false
|
|
}
|
|
}
|
|
|
|
// ReadHandshakeMessage is called by TLS.
|
|
// It blocks until a new handshake message is available.
|
|
func (h *cryptoSetup) ReadHandshakeMessage() ([]byte, error) {
|
|
msg, ok := <-h.messageChan
|
|
if !ok {
|
|
return nil, errors.New("error while handling the handshake message")
|
|
}
|
|
return msg, nil
|
|
}
|
|
|
|
func (h *cryptoSetup) SetReadKey(suite *qtls.CipherSuite, trafficSecret []byte) {
|
|
key := qtls.HkdfExpandLabel(suite.Hash(), trafficSecret, []byte{}, "quic key", suite.KeyLen())
|
|
iv := qtls.HkdfExpandLabel(suite.Hash(), trafficSecret, []byte{}, "quic iv", suite.IVLen())
|
|
hpKey := qtls.HkdfExpandLabel(suite.Hash(), trafficSecret, []byte{}, "quic hp", suite.KeyLen())
|
|
hpDecrypter, err := aes.NewCipher(hpKey)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("error creating new AES cipher: %s", err))
|
|
}
|
|
|
|
h.mutex.Lock()
|
|
switch h.readEncLevel {
|
|
case protocol.EncryptionInitial:
|
|
h.readEncLevel = protocol.EncryptionHandshake
|
|
h.handshakeOpener = newOpener(suite.AEAD(key, iv), hpDecrypter, false)
|
|
h.logger.Debugf("Installed Handshake Read keys")
|
|
case protocol.EncryptionHandshake:
|
|
h.readEncLevel = protocol.Encryption1RTT
|
|
h.opener = newOpener(suite.AEAD(key, iv), hpDecrypter, true)
|
|
h.logger.Debugf("Installed 1-RTT Read keys")
|
|
default:
|
|
panic("unexpected read encryption level")
|
|
}
|
|
h.mutex.Unlock()
|
|
h.receivedReadKey <- struct{}{}
|
|
}
|
|
|
|
func (h *cryptoSetup) SetWriteKey(suite *qtls.CipherSuite, trafficSecret []byte) {
|
|
key := qtls.HkdfExpandLabel(suite.Hash(), trafficSecret, []byte{}, "quic key", suite.KeyLen())
|
|
iv := qtls.HkdfExpandLabel(suite.Hash(), trafficSecret, []byte{}, "quic iv", suite.IVLen())
|
|
hpKey := qtls.HkdfExpandLabel(suite.Hash(), trafficSecret, []byte{}, "quic hp", suite.KeyLen())
|
|
hpEncrypter, err := aes.NewCipher(hpKey)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("error creating new AES cipher: %s", err))
|
|
}
|
|
|
|
h.mutex.Lock()
|
|
switch h.writeEncLevel {
|
|
case protocol.EncryptionInitial:
|
|
h.writeEncLevel = protocol.EncryptionHandshake
|
|
h.handshakeSealer = newSealer(suite.AEAD(key, iv), hpEncrypter, false)
|
|
h.logger.Debugf("Installed Handshake Write keys")
|
|
case protocol.EncryptionHandshake:
|
|
h.writeEncLevel = protocol.Encryption1RTT
|
|
h.sealer = newSealer(suite.AEAD(key, iv), hpEncrypter, true)
|
|
h.logger.Debugf("Installed 1-RTT Write keys")
|
|
default:
|
|
panic("unexpected write encryption level")
|
|
}
|
|
h.mutex.Unlock()
|
|
h.receivedWriteKey <- struct{}{}
|
|
}
|
|
|
|
// WriteRecord is called when TLS writes data
|
|
func (h *cryptoSetup) WriteRecord(p []byte) (int, error) {
|
|
h.mutex.Lock()
|
|
defer h.mutex.Unlock()
|
|
|
|
switch h.writeEncLevel {
|
|
case protocol.EncryptionInitial:
|
|
// assume that the first WriteRecord call contains the ClientHello
|
|
n, err := h.initialStream.Write(p)
|
|
if !h.clientHelloWritten && h.perspective == protocol.PerspectiveClient {
|
|
h.clientHelloWritten = true
|
|
close(h.clientHelloWrittenChan)
|
|
} else {
|
|
// We need additional signaling to properly detect HelloRetryRequests.
|
|
// For servers: when the ServerHello is written.
|
|
// For clients: when a reply is sent in response to a ServerHello.
|
|
h.writeRecord <- struct{}{}
|
|
}
|
|
return n, err
|
|
case protocol.EncryptionHandshake:
|
|
return h.handshakeStream.Write(p)
|
|
case protocol.Encryption1RTT:
|
|
return h.oneRTTStream.Write(p)
|
|
default:
|
|
panic(fmt.Sprintf("unexpected write encryption level: %s", h.writeEncLevel))
|
|
}
|
|
}
|
|
|
|
func (h *cryptoSetup) SendAlert(alert uint8) {
|
|
h.alertChan <- alert
|
|
}
|
|
|
|
func (h *cryptoSetup) GetSealer() (protocol.EncryptionLevel, Sealer) {
|
|
h.mutex.Lock()
|
|
defer h.mutex.Unlock()
|
|
|
|
if h.sealer != nil {
|
|
return protocol.Encryption1RTT, h.sealer
|
|
}
|
|
if h.handshakeSealer != nil {
|
|
return protocol.EncryptionHandshake, h.handshakeSealer
|
|
}
|
|
return protocol.EncryptionInitial, h.initialSealer
|
|
}
|
|
|
|
func (h *cryptoSetup) GetSealerWithEncryptionLevel(level protocol.EncryptionLevel) (Sealer, error) {
|
|
errNoSealer := fmt.Errorf("CryptoSetup: no sealer with encryption level %s", level.String())
|
|
|
|
h.mutex.Lock()
|
|
defer h.mutex.Unlock()
|
|
|
|
switch level {
|
|
case protocol.EncryptionInitial:
|
|
return h.initialSealer, nil
|
|
case protocol.EncryptionHandshake:
|
|
if h.handshakeSealer == nil {
|
|
return nil, errNoSealer
|
|
}
|
|
return h.handshakeSealer, nil
|
|
case protocol.Encryption1RTT:
|
|
if h.sealer == nil {
|
|
return nil, errNoSealer
|
|
}
|
|
return h.sealer, nil
|
|
default:
|
|
return nil, errNoSealer
|
|
}
|
|
}
|
|
|
|
func (h *cryptoSetup) GetOpener(level protocol.EncryptionLevel) (Opener, error) {
|
|
h.mutex.Lock()
|
|
defer h.mutex.Unlock()
|
|
|
|
switch level {
|
|
case protocol.EncryptionInitial:
|
|
return h.initialOpener, nil
|
|
case protocol.EncryptionHandshake:
|
|
if h.handshakeOpener == nil {
|
|
return nil, ErrOpenerNotYetAvailable
|
|
}
|
|
return h.handshakeOpener, nil
|
|
case protocol.Encryption1RTT:
|
|
if h.opener == nil {
|
|
return nil, ErrOpenerNotYetAvailable
|
|
}
|
|
return h.opener, nil
|
|
default:
|
|
return nil, fmt.Errorf("CryptoSetup: no opener with encryption level %s", level)
|
|
}
|
|
}
|
|
|
|
func (h *cryptoSetup) ConnectionState() tls.ConnectionState {
|
|
cs := h.conn.ConnectionState()
|
|
// h.conn is a qtls.Conn, which returns a qtls.ConnectionState.
|
|
// qtls.ConnectionState is identical to the tls.ConnectionState.
|
|
// It contains an unexported field which is used ExportKeyingMaterial().
|
|
// The only way to return a tls.ConnectionState is to use unsafe.
|
|
// In unsafe.go we check that the two objects are actually identical.
|
|
return *(*tls.ConnectionState)(unsafe.Pointer(&cs))
|
|
}
|