hs-tls/core/Network/TLS/Handshake/Process.hs
Vincent Hanquez ef92b6c96f Fix version usage related to downgrading
* properly chose the highest version supported on the server, instead of
  chosing the first that match.
* use the client version in the RSA client exchange instead of the negotiated version
* delay hashing mechanism to serverHello message so that choosing MD5SHA1 or SHA256
  is done after the server chose the version.
2013-12-03 15:17:27 +08:00

103 lines
4.5 KiB
Haskell

-- |
-- Module : Network.TLS.Handshake.Process
-- License : BSD-style
-- Maintainer : Vincent Hanquez <vincent@snarc.org>
-- Stability : experimental
-- Portability : unknown
--
-- process handshake message received
--
module Network.TLS.Handshake.Process
( processHandshake
, startHandshake
, getHandshakeDigest
) where
import Data.ByteString (ByteString)
import Control.Applicative
import Control.Concurrent.MVar
import Control.Monad.Error
import Control.Monad.State (gets)
import Network.TLS.Types (Role(..), invertRole)
import Network.TLS.Util
import Network.TLS.Packet
import Network.TLS.Struct
import Network.TLS.State
import Network.TLS.Context
import Network.TLS.Handshake.State
import Network.TLS.Handshake.Key
import Network.TLS.Extension
import Data.X509
processHandshake :: Context -> Handshake -> IO ()
processHandshake ctx hs = do
role <- usingState_ ctx isClientContext
case hs of
ClientHello cver ran _ _ _ ex _ -> when (role == ServerRole) $ do
mapM_ (usingState_ ctx . processClientExtension) ex
startHandshake ctx cver ran
Certificates certs -> processCertificates role certs
ClientKeyXchg content -> when (role == ServerRole) $ do
processClientKeyXchg ctx content
HsNextProtocolNegotiation selected_protocol ->
when (role == ServerRole) $ usingState_ ctx $ setNegotiatedProtocol selected_protocol
Finished fdata -> processClientFinished ctx fdata
_ -> return ()
let encoded = encodeHandshake hs
when (certVerifyHandshakeMaterial hs) $ usingHState ctx $ addHandshakeMessage encoded
when (finishHandshakeTypeMaterial $ typeOfHandshake hs) $ usingHState ctx $ updateHandshakeDigest encoded
where -- secure renegotiation
processClientExtension (0xff01, content) = do
v <- getVerifiedData ClientRole
let bs = extensionEncode (SecureRenegotiation v Nothing)
unless (bs `bytesEq` content) $ throwError $ Error_Protocol ("client verified data not matching: " ++ show v ++ ":" ++ show content, True, HandshakeFailure)
setSecureRenegotiation True
-- unknown extensions
processClientExtension _ = return ()
processCertificates :: Role -> CertificateChain -> IO ()
processCertificates ServerRole (CertificateChain []) = return ()
processCertificates ClientRole (CertificateChain []) =
throwCore $ Error_Protocol ("server certificate missing", True, HandshakeFailure)
processCertificates role (CertificateChain (c:_))
| role == ClientRole = usingHState ctx $ setPublicKey pubkey
| otherwise = usingHState ctx $ setClientPublicKey pubkey
where pubkey = certPubKey $ getCertificate c
-- process the client key exchange message. the protocol expects the initial
-- client version received in ClientHello, not the negotiated version.
-- in case the version mismatch, generate a random master secret
processClientKeyXchg :: Context -> ByteString -> IO ()
processClientKeyXchg ctx encryptedPremaster = do
(rver, role, random) <- usingState_ ctx $ do
(,,) <$> getVersion <*> isClientContext <*> genRandom 48
ePremaster <- decryptRSA ctx encryptedPremaster
usingHState ctx $ do
expectedVer <- gets hstClientVersion
case ePremaster of
Left _ -> setMasterSecretFromPre rver role random
Right premaster -> case decodePreMasterSecret premaster of
Left _ -> setMasterSecretFromPre rver role random
Right (ver, _)
| ver /= expectedVer -> setMasterSecretFromPre rver role random
| otherwise -> setMasterSecretFromPre rver role premaster
processClientFinished :: Context -> FinishedData -> IO ()
processClientFinished ctx fdata = do
(cc,ver) <- usingState_ ctx $ (,) <$> isClientContext <*> getVersion
expected <- usingHState ctx $ getHandshakeDigest ver $ invertRole cc
when (expected /= fdata) $ do
throwCore $ Error_Protocol("bad record mac", True, BadRecordMac)
usingState_ ctx $ updateVerifiedData ServerRole fdata
return ()
startHandshake :: Context -> Version -> ClientRandom -> IO ()
startHandshake ctx ver crand = do
-- FIXME check if handshake is already not null
liftIO $ modifyMVar_ (ctxHandshake ctx) $ \hs ->
case hs of
Nothing -> return $ Just $ newEmptyHandshake ver crand
Just _ -> return hs