2017-12-12 02:51:45 +00:00
package handshake
import (
"bytes"
"crypto/rand"
"encoding/binary"
"errors"
"io"
2018-01-03 19:19:49 +00:00
"net"
2017-12-12 02:51:45 +00:00
"sync"
2018-01-03 19:19:49 +00:00
"github.com/lucas-clemente/quic-go/internal/crypto"
"github.com/lucas-clemente/quic-go/internal/protocol"
"github.com/lucas-clemente/quic-go/internal/utils"
2017-12-12 02:51:45 +00:00
"github.com/lucas-clemente/quic-go/qerr"
)
2018-01-03 19:19:49 +00:00
// QuicCryptoKeyDerivationFunction is used for key derivation
type QuicCryptoKeyDerivationFunction func ( forwardSecure bool , sharedSecret , nonces [ ] byte , connID protocol . ConnectionID , chlo [ ] byte , scfg [ ] byte , cert [ ] byte , divNonce [ ] byte , pers protocol . Perspective ) ( crypto . AEAD , error )
2017-12-12 02:51:45 +00:00
// KeyExchangeFunction is used to make a new KEX
type KeyExchangeFunction func ( ) crypto . KeyExchange
// The CryptoSetupServer handles all things crypto for the Session
type cryptoSetupServer struct {
2018-01-20 18:07:01 +00:00
mutex sync . RWMutex
2017-12-12 02:51:45 +00:00
connID protocol . ConnectionID
2018-01-03 19:19:49 +00:00
remoteAddr net . Addr
2017-12-12 02:51:45 +00:00
scfg * ServerConfig
diversificationNonce [ ] byte
2018-01-03 19:19:49 +00:00
version protocol . VersionNumber
supportedVersions [ ] protocol . VersionNumber
acceptSTKCallback func ( net . Addr , * Cookie ) bool
nullAEAD crypto . AEAD
2017-12-12 02:51:45 +00:00
secureAEAD crypto . AEAD
forwardSecureAEAD crypto . AEAD
receivedForwardSecurePacket bool
receivedSecurePacket bool
2018-01-03 19:19:49 +00:00
sentSHLO chan struct { } // this channel is closed as soon as the SHLO has been written
2017-12-12 02:51:45 +00:00
2018-01-03 19:19:49 +00:00
receivedParams bool
paramsChan chan <- TransportParameters
2018-01-20 18:07:01 +00:00
handshakeEvent chan <- struct { }
2018-01-03 19:19:49 +00:00
keyDerivation QuicCryptoKeyDerivationFunction
2017-12-12 02:51:45 +00:00
keyExchange KeyExchangeFunction
cryptoStream io . ReadWriter
2018-01-03 19:19:49 +00:00
params * TransportParameters
2017-12-12 02:51:45 +00:00
2018-01-20 18:07:01 +00:00
sni string // need to fill out the ConnectionState
2017-12-12 02:51:45 +00:00
}
var _ CryptoSetup = & cryptoSetupServer { }
2018-01-03 19:19:49 +00:00
// ErrHOLExperiment is returned when the client sends the FHL2 tag in the CHLO.
// This is an experiment implemented by Chrome in QUIC 36, which we don't support.
2017-12-12 02:51:45 +00:00
// TODO: remove this when dropping support for QUIC 36
var ErrHOLExperiment = qerr . Error ( qerr . InvalidCryptoMessageParameter , "HOL experiment. Unsupported" )
2018-01-03 19:19:49 +00:00
// ErrNSTPExperiment is returned when the client sends the NSTP tag in the CHLO.
// This is an experiment implemented by Chrome in QUIC 38, which we don't support at this point.
var ErrNSTPExperiment = qerr . Error ( qerr . InvalidCryptoMessageParameter , "NSTP experiment. Unsupported" )
2017-12-12 02:51:45 +00:00
// NewCryptoSetup creates a new CryptoSetup instance for a server
func NewCryptoSetup (
2018-01-03 19:19:49 +00:00
cryptoStream io . ReadWriter ,
2017-12-12 02:51:45 +00:00
connID protocol . ConnectionID ,
2018-01-03 19:19:49 +00:00
remoteAddr net . Addr ,
2017-12-12 02:51:45 +00:00
version protocol . VersionNumber ,
scfg * ServerConfig ,
2018-01-03 19:19:49 +00:00
params * TransportParameters ,
supportedVersions [ ] protocol . VersionNumber ,
acceptSTK func ( net . Addr , * Cookie ) bool ,
paramsChan chan <- TransportParameters ,
2018-01-20 18:07:01 +00:00
handshakeEvent chan <- struct { } ,
2017-12-12 02:51:45 +00:00
) ( CryptoSetup , error ) {
2018-01-03 19:19:49 +00:00
nullAEAD , err := crypto . NewNullAEAD ( protocol . PerspectiveServer , connID , version )
if err != nil {
return nil , err
}
2017-12-12 02:51:45 +00:00
return & cryptoSetupServer {
2018-01-03 19:19:49 +00:00
cryptoStream : cryptoStream ,
connID : connID ,
remoteAddr : remoteAddr ,
version : version ,
supportedVersions : supportedVersions ,
scfg : scfg ,
keyDerivation : crypto . DeriveQuicCryptoAESKeys ,
keyExchange : getEphermalKEX ,
nullAEAD : nullAEAD ,
params : params ,
acceptSTKCallback : acceptSTK ,
sentSHLO : make ( chan struct { } ) ,
paramsChan : paramsChan ,
2018-01-20 18:07:01 +00:00
handshakeEvent : handshakeEvent ,
2017-12-12 02:51:45 +00:00
} , nil
}
// HandleCryptoStream reads and writes messages on the crypto stream
func ( h * cryptoSetupServer ) HandleCryptoStream ( ) error {
for {
var chloData bytes . Buffer
2018-01-03 19:19:49 +00:00
message , err := ParseHandshakeMessage ( io . TeeReader ( h . cryptoStream , & chloData ) )
2017-12-12 02:51:45 +00:00
if err != nil {
return qerr . HandshakeFailed
}
2018-01-03 19:19:49 +00:00
if message . Tag != TagCHLO {
2017-12-12 02:51:45 +00:00
return qerr . InvalidCryptoMessageType
}
2018-01-03 19:19:49 +00:00
utils . Debugf ( "Got %s" , message )
done , err := h . handleMessage ( chloData . Bytes ( ) , message . Data )
2017-12-12 02:51:45 +00:00
if err != nil {
return err
}
if done {
return nil
}
}
}
func ( h * cryptoSetupServer ) handleMessage ( chloData [ ] byte , cryptoData map [ Tag ] [ ] byte ) ( bool , error ) {
if _ , isHOLExperiment := cryptoData [ TagFHL2 ] ; isHOLExperiment {
return false , ErrHOLExperiment
}
2018-01-03 19:19:49 +00:00
if _ , isNSTPExperiment := cryptoData [ TagNSTP ] ; isNSTPExperiment {
return false , ErrNSTPExperiment
}
2017-12-12 02:51:45 +00:00
sniSlice , ok := cryptoData [ TagSNI ]
if ! ok {
return false , qerr . Error ( qerr . CryptoMessageParameterNotFound , "SNI required" )
}
sni := string ( sniSlice )
if sni == "" {
return false , qerr . Error ( qerr . CryptoMessageParameterNotFound , "SNI required" )
}
2018-01-20 18:07:01 +00:00
h . sni = sni
2017-12-12 02:51:45 +00:00
// prevent version downgrade attacks
// see https://groups.google.com/a/chromium.org/forum/#!topic/proto-quic/N-de9j63tCk for a discussion and examples
verSlice , ok := cryptoData [ TagVER ]
if ! ok {
return false , qerr . Error ( qerr . InvalidCryptoMessageParameter , "client hello missing version tag" )
}
if len ( verSlice ) != 4 {
return false , qerr . Error ( qerr . InvalidCryptoMessageParameter , "incorrect version tag" )
}
2018-01-03 19:19:49 +00:00
ver := protocol . VersionNumber ( binary . BigEndian . Uint32 ( verSlice ) )
2017-12-12 02:51:45 +00:00
// If the client's preferred version is not the version we are currently speaking, then the client went through a version negotiation. In this case, we need to make sure that we actually do not support this version and that it wasn't a downgrade attack.
2018-01-03 19:19:49 +00:00
if ver != h . version && protocol . IsSupportedVersion ( h . supportedVersions , ver ) {
2017-12-12 02:51:45 +00:00
return false , qerr . Error ( qerr . VersionNegotiationMismatch , "Downgrade attack detected" )
}
var reply [ ] byte
var err error
certUncompressed , err := h . scfg . certChain . GetLeafCert ( sni )
if err != nil {
return false , err
}
2018-01-03 19:19:49 +00:00
params , err := readHelloMap ( cryptoData )
if err != nil {
return false , err
}
// blocks until the session has received the parameters
if ! h . receivedParams {
h . receivedParams = true
h . paramsChan <- * params
}
2017-12-12 02:51:45 +00:00
if ! h . isInchoateCHLO ( cryptoData , certUncompressed ) {
// We have a CHLO with a proper server config ID, do a 0-RTT handshake
reply , err = h . handleCHLO ( sni , chloData , cryptoData )
if err != nil {
return false , err
}
2018-01-03 19:19:49 +00:00
if _ , err := h . cryptoStream . Write ( reply ) ; err != nil {
2017-12-12 02:51:45 +00:00
return false , err
}
2018-01-20 18:07:01 +00:00
h . handshakeEvent <- struct { } { }
2018-01-03 19:19:49 +00:00
close ( h . sentSHLO )
2017-12-12 02:51:45 +00:00
return true , nil
}
// We have an inchoate or non-matching CHLO, we now send a rejection
reply , err = h . handleInchoateCHLO ( sni , chloData , cryptoData )
if err != nil {
return false , err
}
_ , err = h . cryptoStream . Write ( reply )
2018-01-03 19:19:49 +00:00
return false , err
2017-12-12 02:51:45 +00:00
}
// Open a message
func ( h * cryptoSetupServer ) Open ( dst , src [ ] byte , packetNumber protocol . PacketNumber , associatedData [ ] byte ) ( [ ] byte , protocol . EncryptionLevel , error ) {
h . mutex . RLock ( )
defer h . mutex . RUnlock ( )
if h . forwardSecureAEAD != nil {
res , err := h . forwardSecureAEAD . Open ( dst , src , packetNumber , associatedData )
if err == nil {
2018-01-03 19:19:49 +00:00
if ! h . receivedForwardSecurePacket { // this is the first forward secure packet we receive from the client
h . receivedForwardSecurePacket = true
2018-01-20 18:07:01 +00:00
// wait for the send on the handshakeEvent chan
2018-01-03 19:19:49 +00:00
<- h . sentSHLO
2018-01-20 18:07:01 +00:00
close ( h . handshakeEvent )
2018-01-03 19:19:49 +00:00
}
2017-12-12 02:51:45 +00:00
return res , protocol . EncryptionForwardSecure , nil
}
if h . receivedForwardSecurePacket {
return nil , protocol . EncryptionUnspecified , err
}
}
if h . secureAEAD != nil {
res , err := h . secureAEAD . Open ( dst , src , packetNumber , associatedData )
if err == nil {
h . receivedSecurePacket = true
return res , protocol . EncryptionSecure , nil
}
if h . receivedSecurePacket {
return nil , protocol . EncryptionUnspecified , err
}
}
2018-01-03 19:19:49 +00:00
res , err := h . nullAEAD . Open ( dst , src , packetNumber , associatedData )
2017-12-12 02:51:45 +00:00
if err != nil {
return res , protocol . EncryptionUnspecified , err
}
return res , protocol . EncryptionUnencrypted , err
}
func ( h * cryptoSetupServer ) GetSealer ( ) ( protocol . EncryptionLevel , Sealer ) {
h . mutex . RLock ( )
defer h . mutex . RUnlock ( )
2018-01-03 19:19:49 +00:00
if h . forwardSecureAEAD != nil {
return protocol . EncryptionForwardSecure , h . forwardSecureAEAD
}
return protocol . EncryptionUnencrypted , h . nullAEAD
}
2017-12-12 02:51:45 +00:00
2018-01-03 19:19:49 +00:00
func ( h * cryptoSetupServer ) GetSealerForCryptoStream ( ) ( protocol . EncryptionLevel , Sealer ) {
h . mutex . RLock ( )
defer h . mutex . RUnlock ( )
if h . secureAEAD != nil {
return protocol . EncryptionSecure , h . secureAEAD
2017-12-12 02:51:45 +00:00
}
2018-01-03 19:19:49 +00:00
return protocol . EncryptionUnencrypted , h . nullAEAD
2017-12-12 02:51:45 +00:00
}
func ( h * cryptoSetupServer ) GetSealerWithEncryptionLevel ( encLevel protocol . EncryptionLevel ) ( Sealer , error ) {
2018-01-03 19:19:49 +00:00
h . mutex . RLock ( )
defer h . mutex . RUnlock ( )
2017-12-12 02:51:45 +00:00
switch encLevel {
case protocol . EncryptionUnencrypted :
2018-01-03 19:19:49 +00:00
return h . nullAEAD , nil
2017-12-12 02:51:45 +00:00
case protocol . EncryptionSecure :
if h . secureAEAD == nil {
return nil , errors . New ( "CryptoSetupServer: no secureAEAD" )
}
2018-01-03 19:19:49 +00:00
return h . secureAEAD , nil
2017-12-12 02:51:45 +00:00
case protocol . EncryptionForwardSecure :
if h . forwardSecureAEAD == nil {
return nil , errors . New ( "CryptoSetupServer: no forwardSecureAEAD" )
}
2018-01-03 19:19:49 +00:00
return h . forwardSecureAEAD , nil
2017-12-12 02:51:45 +00:00
}
return nil , errors . New ( "CryptoSetupServer: no encryption level specified" )
}
func ( h * cryptoSetupServer ) isInchoateCHLO ( cryptoData map [ Tag ] [ ] byte , cert [ ] byte ) bool {
if _ , ok := cryptoData [ TagPUBS ] ; ! ok {
return true
}
scid , ok := cryptoData [ TagSCID ]
if ! ok || ! bytes . Equal ( h . scfg . ID , scid ) {
return true
}
xlctTag , ok := cryptoData [ TagXLCT ]
if ! ok || len ( xlctTag ) != 8 {
return true
}
xlct := binary . LittleEndian . Uint64 ( xlctTag )
if crypto . HashCert ( cert ) != xlct {
return true
}
2018-01-03 19:19:49 +00:00
return ! h . acceptSTK ( cryptoData [ TagSTK ] )
}
func ( h * cryptoSetupServer ) acceptSTK ( token [ ] byte ) bool {
stk , err := h . scfg . cookieGenerator . DecodeToken ( token )
if err != nil {
2017-12-12 02:51:45 +00:00
utils . Debugf ( "STK invalid: %s" , err . Error ( ) )
2018-01-03 19:19:49 +00:00
return false
2017-12-12 02:51:45 +00:00
}
2018-01-03 19:19:49 +00:00
return h . acceptSTKCallback ( h . remoteAddr , stk )
2017-12-12 02:51:45 +00:00
}
func ( h * cryptoSetupServer ) handleInchoateCHLO ( sni string , chlo [ ] byte , cryptoData map [ Tag ] [ ] byte ) ( [ ] byte , error ) {
2018-01-03 19:19:49 +00:00
token , err := h . scfg . cookieGenerator . NewToken ( h . remoteAddr )
2017-12-12 02:51:45 +00:00
if err != nil {
return nil , err
}
replyMap := map [ Tag ] [ ] byte {
TagSCFG : h . scfg . Get ( ) ,
TagSTK : token ,
TagSVID : [ ] byte ( "quic-go" ) ,
}
2018-01-03 19:19:49 +00:00
if h . acceptSTK ( cryptoData [ TagSTK ] ) {
2017-12-12 02:51:45 +00:00
proof , err := h . scfg . Sign ( sni , chlo )
if err != nil {
return nil , err
}
commonSetHashes := cryptoData [ TagCCS ]
cachedCertsHashes := cryptoData [ TagCCRT ]
certCompressed , err := h . scfg . GetCertsCompressed ( sni , commonSetHashes , cachedCertsHashes )
if err != nil {
return nil , err
}
// Token was valid, send more details
replyMap [ TagPROF ] = proof
replyMap [ TagCERT ] = certCompressed
}
2018-01-03 19:19:49 +00:00
message := HandshakeMessage {
Tag : TagREJ ,
Data : replyMap ,
}
2017-12-12 02:51:45 +00:00
var serverReply bytes . Buffer
2018-01-03 19:19:49 +00:00
message . Write ( & serverReply )
utils . Debugf ( "Sending %s" , message )
2017-12-12 02:51:45 +00:00
return serverReply . Bytes ( ) , nil
}
func ( h * cryptoSetupServer ) handleCHLO ( sni string , data [ ] byte , cryptoData map [ Tag ] [ ] byte ) ( [ ] byte , error ) {
// We have a CHLO matching our server config, we can continue with the 0-RTT handshake
sharedSecret , err := h . scfg . kex . CalculateSharedKey ( cryptoData [ TagPUBS ] )
if err != nil {
return nil , err
}
h . mutex . Lock ( )
defer h . mutex . Unlock ( )
certUncompressed , err := h . scfg . certChain . GetLeafCert ( sni )
if err != nil {
return nil , err
}
serverNonce := make ( [ ] byte , 32 )
if _ , err = rand . Read ( serverNonce ) ; err != nil {
return nil , err
}
h . diversificationNonce = make ( [ ] byte , 32 )
if _ , err = rand . Read ( h . diversificationNonce ) ; err != nil {
return nil , err
}
clientNonce := cryptoData [ TagNONC ]
err = h . validateClientNonce ( clientNonce )
if err != nil {
return nil , err
}
aead := cryptoData [ TagAEAD ]
if ! bytes . Equal ( aead , [ ] byte ( "AESG" ) ) {
return nil , qerr . Error ( qerr . CryptoNoSupport , "Unsupported AEAD or KEXS" )
}
kexs := cryptoData [ TagKEXS ]
if ! bytes . Equal ( kexs , [ ] byte ( "C255" ) ) {
return nil , qerr . Error ( qerr . CryptoNoSupport , "Unsupported AEAD or KEXS" )
}
h . secureAEAD , err = h . keyDerivation (
false ,
sharedSecret ,
clientNonce ,
h . connID ,
data ,
h . scfg . Get ( ) ,
certUncompressed ,
h . diversificationNonce ,
protocol . PerspectiveServer ,
)
if err != nil {
return nil , err
}
2018-01-20 18:07:01 +00:00
h . handshakeEvent <- struct { } { }
2017-12-12 02:51:45 +00:00
// Generate a new curve instance to derive the forward secure key
var fsNonce bytes . Buffer
fsNonce . Write ( clientNonce )
fsNonce . Write ( serverNonce )
ephermalKex := h . keyExchange ( )
ephermalSharedSecret , err := ephermalKex . CalculateSharedKey ( cryptoData [ TagPUBS ] )
if err != nil {
return nil , err
}
h . forwardSecureAEAD , err = h . keyDerivation (
true ,
ephermalSharedSecret ,
fsNonce . Bytes ( ) ,
h . connID ,
data ,
h . scfg . Get ( ) ,
certUncompressed ,
nil ,
protocol . PerspectiveServer ,
)
if err != nil {
return nil , err
}
2018-01-03 19:19:49 +00:00
replyMap := h . params . getHelloMap ( )
2017-12-12 02:51:45 +00:00
// add crypto parameters
2018-01-03 19:19:49 +00:00
verTag := & bytes . Buffer { }
for _ , v := range protocol . GetGreasedVersions ( h . supportedVersions ) {
utils . BigEndian . WriteUint32 ( verTag , uint32 ( v ) )
}
2017-12-12 02:51:45 +00:00
replyMap [ TagPUBS ] = ephermalKex . PublicKey ( )
replyMap [ TagSNO ] = serverNonce
2018-01-03 19:19:49 +00:00
replyMap [ TagVER ] = verTag . Bytes ( )
2017-12-12 02:51:45 +00:00
// note that the SHLO *has* to fit into one packet
2018-01-03 19:19:49 +00:00
message := HandshakeMessage {
Tag : TagSHLO ,
Data : replyMap ,
}
2017-12-12 02:51:45 +00:00
var reply bytes . Buffer
2018-01-03 19:19:49 +00:00
message . Write ( & reply )
utils . Debugf ( "Sending %s" , message )
2017-12-12 02:51:45 +00:00
return reply . Bytes ( ) , nil
}
// DiversificationNonce returns the diversification nonce
func ( h * cryptoSetupServer ) DiversificationNonce ( ) [ ] byte {
return h . diversificationNonce
}
2018-01-03 19:19:49 +00:00
func ( h * cryptoSetupServer ) SetDiversificationNonce ( data [ ] byte ) {
2017-12-12 02:51:45 +00:00
panic ( "not needed for cryptoSetupServer" )
}
2018-01-20 18:07:01 +00:00
func ( h * cryptoSetupServer ) ConnectionState ( ) ConnectionState {
h . mutex . Lock ( )
defer h . mutex . Unlock ( )
return ConnectionState {
ServerName : h . sni ,
HandshakeComplete : h . receivedForwardSecurePacket ,
}
}
2017-12-12 02:51:45 +00:00
func ( h * cryptoSetupServer ) validateClientNonce ( nonce [ ] byte ) error {
if len ( nonce ) != 32 {
return qerr . Error ( qerr . InvalidCryptoMessageParameter , "invalid client nonce length" )
}
if ! bytes . Equal ( nonce [ 4 : 12 ] , h . scfg . obit ) {
return qerr . Error ( qerr . InvalidCryptoMessageParameter , "OBIT not matching" )
}
return nil
}