2017-05-27 14:30:11 -05:00
package handshake
import (
"bytes"
"crypto/rand"
"encoding/binary"
"errors"
"io"
"net"
"sync"
"github.com/lucas-clemente/quic-go/crypto"
2017-07-27 17:11:56 -05:00
"github.com/lucas-clemente/quic-go/internal/utils"
2017-05-27 14:30:11 -05:00
"github.com/lucas-clemente/quic-go/protocol"
"github.com/lucas-clemente/quic-go/qerr"
)
// KeyDerivationFunction is used for key derivation
type KeyDerivationFunction func ( forwardSecure bool , sharedSecret , nonces [ ] byte , connID protocol . ConnectionID , chlo [ ] byte , scfg [ ] byte , cert [ ] byte , divNonce [ ] byte , pers protocol . Perspective ) ( crypto . AEAD , error )
// 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 {
connID protocol . ConnectionID
remoteAddr net . Addr
scfg * ServerConfig
stkGenerator * STKGenerator
diversificationNonce [ ] byte
version protocol . VersionNumber
supportedVersions [ ] protocol . VersionNumber
acceptSTKCallback func ( net . Addr , * STK ) bool
nullAEAD crypto . AEAD
secureAEAD crypto . AEAD
forwardSecureAEAD crypto . AEAD
receivedForwardSecurePacket bool
sentSHLO bool
receivedSecurePacket bool
aeadChanged chan <- protocol . EncryptionLevel
keyDerivation KeyDerivationFunction
keyExchange KeyExchangeFunction
cryptoStream io . ReadWriter
connectionParameters ConnectionParametersManager
mutex sync . RWMutex
}
var _ CryptoSetup = & cryptoSetupServer { }
2017-08-23 11:32:08 -05: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-05-27 14:30:11 -05:00
// TODO: remove this when dropping support for QUIC 36
var ErrHOLExperiment = qerr . Error ( qerr . InvalidCryptoMessageParameter , "HOL experiment. Unsupported" )
2017-08-23 11:32:08 -05: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-05-27 14:30:11 -05:00
// NewCryptoSetup creates a new CryptoSetup instance for a server
func NewCryptoSetup (
connID protocol . ConnectionID ,
remoteAddr net . Addr ,
version protocol . VersionNumber ,
scfg * ServerConfig ,
cryptoStream io . ReadWriter ,
connectionParametersManager ConnectionParametersManager ,
supportedVersions [ ] protocol . VersionNumber ,
acceptSTK func ( net . Addr , * STK ) bool ,
aeadChanged chan <- protocol . EncryptionLevel ,
) ( CryptoSetup , error ) {
stkGenerator , err := NewSTKGenerator ( )
if err != nil {
return nil , err
}
return & cryptoSetupServer {
connID : connID ,
remoteAddr : remoteAddr ,
version : version ,
supportedVersions : supportedVersions ,
scfg : scfg ,
stkGenerator : stkGenerator ,
keyDerivation : crypto . DeriveKeysAESGCM ,
keyExchange : getEphermalKEX ,
nullAEAD : crypto . NewNullAEAD ( protocol . PerspectiveServer , version ) ,
cryptoStream : cryptoStream ,
connectionParameters : connectionParametersManager ,
acceptSTKCallback : acceptSTK ,
aeadChanged : aeadChanged ,
} , nil
}
// HandleCryptoStream reads and writes messages on the crypto stream
func ( h * cryptoSetupServer ) HandleCryptoStream ( ) error {
for {
var chloData bytes . Buffer
message , err := ParseHandshakeMessage ( io . TeeReader ( h . cryptoStream , & chloData ) )
if err != nil {
return qerr . HandshakeFailed
}
if message . Tag != TagCHLO {
return qerr . InvalidCryptoMessageType
}
utils . Debugf ( "Got %s" , message )
done , err := h . handleMessage ( chloData . Bytes ( ) , message . Data )
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
}
2017-08-23 11:32:08 -05:00
if _ , isNSTPExperiment := cryptoData [ TagNSTP ] ; isNSTPExperiment {
return false , ErrNSTPExperiment
}
2017-05-27 14:30:11 -05: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" )
}
// 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" )
}
verTag := binary . LittleEndian . Uint32 ( verSlice )
ver := protocol . VersionTagToNumber ( verTag )
// 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.
if ver != h . version && protocol . IsSupportedVersion ( h . supportedVersions , ver ) {
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
}
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
}
_ , err = h . cryptoStream . Write ( reply )
if err != nil {
return false , err
}
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 )
return false , err
}
// 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 {
if ! h . receivedForwardSecurePacket { // this is the first forward secure packet we receive from the client
h . receivedForwardSecurePacket = true
close ( h . aeadChanged )
}
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
}
}
res , err := h . nullAEAD . Open ( dst , src , packetNumber , associatedData )
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 ( )
2017-07-27 17:11:56 -05:00
if h . forwardSecureAEAD != nil {
2017-05-27 14:30:11 -05:00
return protocol . EncryptionForwardSecure , h . sealForwardSecure
2017-07-27 17:11:56 -05:00
}
return protocol . EncryptionUnencrypted , h . sealUnencrypted
}
func ( h * cryptoSetupServer ) GetSealerForCryptoStream ( ) ( protocol . EncryptionLevel , Sealer ) {
h . mutex . RLock ( )
defer h . mutex . RUnlock ( )
if h . secureAEAD != nil {
2017-05-27 14:30:11 -05:00
return protocol . EncryptionSecure , h . sealSecure
}
return protocol . EncryptionUnencrypted , h . sealUnencrypted
}
func ( h * cryptoSetupServer ) GetSealerWithEncryptionLevel ( encLevel protocol . EncryptionLevel ) ( Sealer , error ) {
h . mutex . RLock ( )
defer h . mutex . RUnlock ( )
switch encLevel {
case protocol . EncryptionUnencrypted :
return h . sealUnencrypted , nil
case protocol . EncryptionSecure :
if h . secureAEAD == nil {
return nil , errors . New ( "CryptoSetupServer: no secureAEAD" )
}
return h . sealSecure , nil
case protocol . EncryptionForwardSecure :
if h . forwardSecureAEAD == nil {
return nil , errors . New ( "CryptoSetupServer: no forwardSecureAEAD" )
}
return h . sealForwardSecure , nil
}
return nil , errors . New ( "CryptoSetupServer: no encryption level specified" )
}
func ( h * cryptoSetupServer ) sealUnencrypted ( dst , src [ ] byte , packetNumber protocol . PacketNumber , associatedData [ ] byte ) [ ] byte {
return h . nullAEAD . Seal ( dst , src , packetNumber , associatedData )
}
func ( h * cryptoSetupServer ) sealSecure ( dst , src [ ] byte , packetNumber protocol . PacketNumber , associatedData [ ] byte ) [ ] byte {
return h . secureAEAD . Seal ( dst , src , packetNumber , associatedData )
}
func ( h * cryptoSetupServer ) sealForwardSecure ( dst , src [ ] byte , packetNumber protocol . PacketNumber , associatedData [ ] byte ) [ ] byte {
return h . forwardSecureAEAD . Seal ( dst , src , packetNumber , associatedData )
}
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
}
return ! h . acceptSTK ( cryptoData [ TagSTK ] )
}
func ( h * cryptoSetupServer ) acceptSTK ( token [ ] byte ) bool {
stk , err := h . stkGenerator . DecodeToken ( token )
if err != nil {
utils . Debugf ( "STK invalid: %s" , err . Error ( ) )
return false
}
return h . acceptSTKCallback ( h . remoteAddr , stk )
}
func ( h * cryptoSetupServer ) handleInchoateCHLO ( sni string , chlo [ ] byte , cryptoData map [ Tag ] [ ] byte ) ( [ ] byte , error ) {
if len ( chlo ) < protocol . ClientHelloMinimumSize {
return nil , qerr . Error ( qerr . CryptoInvalidValueLength , "CHLO too small" )
}
token , err := h . stkGenerator . NewToken ( h . remoteAddr )
if err != nil {
return nil , err
}
replyMap := map [ Tag ] [ ] byte {
TagSCFG : h . scfg . Get ( ) ,
TagSTK : token ,
TagSVID : [ ] byte ( "quic-go" ) ,
}
if h . acceptSTK ( cryptoData [ TagSTK ] ) {
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
}
message := HandshakeMessage {
Tag : TagREJ ,
Data : replyMap ,
}
var serverReply bytes . Buffer
message . Write ( & serverReply )
utils . Debugf ( "Sending %s" , message )
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
}
h . aeadChanged <- protocol . EncryptionSecure
// 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
}
err = h . connectionParameters . SetFromMap ( cryptoData )
if err != nil {
return nil , err
}
replyMap , err := h . connectionParameters . GetHelloMap ( )
if err != nil {
return nil , err
}
// add crypto parameters
verTag := & bytes . Buffer { }
for _ , v := range h . supportedVersions {
utils . WriteUint32 ( verTag , protocol . VersionNumberToTag ( v ) )
}
replyMap [ TagPUBS ] = ephermalKex . PublicKey ( )
replyMap [ TagSNO ] = serverNonce
replyMap [ TagVER ] = verTag . Bytes ( )
// note that the SHLO *has* to fit into one packet
message := HandshakeMessage {
Tag : TagSHLO ,
Data : replyMap ,
}
var reply bytes . Buffer
message . Write ( & reply )
utils . Debugf ( "Sending %s" , message )
h . aeadChanged <- protocol . EncryptionForwardSecure
return reply . Bytes ( ) , nil
}
// DiversificationNonce returns the diversification nonce
func ( h * cryptoSetupServer ) DiversificationNonce ( ) [ ] byte {
return h . diversificationNonce
}
func ( h * cryptoSetupServer ) SetDiversificationNonce ( data [ ] byte ) {
panic ( "not needed for cryptoSetupServer" )
}
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
}