hs-tls/core/Network/TLS/Handshake/Client.hs

295 lines
14 KiB
Haskell
Raw Normal View History

2012-08-18 22:05:56 +00:00
{-# LANGUAGE DeriveDataTypeable, OverloadedStrings #-}
-- |
-- Module : Network.TLS.Handshake.Client
-- License : BSD-style
-- Maintainer : Vincent Hanquez <vincent@snarc.org>
-- Stability : experimental
-- Portability : unknown
--
module Network.TLS.Handshake.Client
( handshakeClient
) where
import Network.TLS.Crypto
import Network.TLS.Context
import Network.TLS.Struct
import Network.TLS.Cipher
import Network.TLS.Compression
import Network.TLS.Packet
import Network.TLS.Extension
import Network.TLS.IO
import Network.TLS.State hiding (getNegotiatedProtocol)
import Network.TLS.Measurement
import Network.TLS.Wire (encodeWord16)
2013-07-24 05:50:56 +00:00
import Network.TLS.Util (bytesEq)
import Network.TLS.Types
import Network.TLS.X509
2012-08-18 22:05:56 +00:00
import Data.Maybe
import Data.List (find)
import qualified Data.ByteString as B
import Data.ByteString.Char8 ()
import Control.Applicative ((<$>))
import Control.Monad.State
2013-07-24 05:50:56 +00:00
import Control.Monad.Error
import Control.Exception (SomeException)
import qualified Control.Exception as E
2012-08-18 22:05:56 +00:00
import Network.TLS.Handshake.Common
import Network.TLS.Handshake.Certificate
import Network.TLS.Handshake.Signature
import Network.TLS.Handshake.Key
2013-07-20 06:18:16 +00:00
import Network.TLS.Handshake.State
2012-08-18 22:05:56 +00:00
-- client part of handshake. send a bunch of handshake of client
-- values intertwined with response from the server.
handshakeClient :: MonadIO m => ClientParams -> Context -> m ()
handshakeClient cparams ctx = do
2012-10-20 07:56:53 +00:00
updateMeasure ctx incrementNbHandshakes
sentExtensions <- sendClientHello
recvServerHello sentExtensions
sessionResuming <- usingState_ ctx isSessionResuming
if sessionResuming
2013-07-23 07:14:48 +00:00
then sendChangeCipherAndFinish ctx ClientRole
2012-10-20 07:56:53 +00:00
else do sendClientData cparams ctx
2013-07-23 07:14:48 +00:00
sendChangeCipherAndFinish ctx ClientRole
2012-10-20 07:56:53 +00:00
recvChangeCipherAndFinish ctx
handshakeTerminate ctx
2013-07-10 06:20:58 +00:00
where params = ctxParams ctx
allowedvers = pAllowedVersions params
ciphers = pCiphers params
compressions = pCompressions params
getExtensions = sequence [sniExtension,secureReneg,npnExtention] >>= return . catMaybes
toExtensionRaw :: Extension e => e -> ExtensionRaw
toExtensionRaw ext = (extensionID ext, extensionEncode ext)
secureReneg =
if pUseSecureRenegotiation params
2013-07-24 05:50:56 +00:00
then usingState_ ctx (getVerifiedData ClientRole) >>= \vd -> return $ Just $ toExtensionRaw $ SecureRenegotiation vd Nothing
2013-07-10 06:20:58 +00:00
else return Nothing
npnExtention = if isJust $ onNPNServerSuggest cparams
then return $ Just $ toExtensionRaw $ NextProtocolNegotiation []
else return Nothing
sniExtension = return ((\h -> toExtensionRaw $ ServerName [(ServerNameHostName h)]) <$> clientUseServerName cparams)
sendClientHello = do
crand <- getStateRNG ctx 32 >>= return . ClientRandom
let clientSession = Session . maybe Nothing (Just . fst) $ clientWantSessionResume cparams
extensions <- getExtensions
usingState_ ctx (startHandshakeClient (pConnectVersion params) crand)
sendPacket ctx $ Handshake
[ ClientHello (pConnectVersion params) crand clientSession (map cipherID ciphers)
(map compressionID compressions) extensions Nothing
]
return $ map fst extensions
expectChangeCipher ChangeCipherSpec = return $ RecvStateHandshake expectFinish
expectChangeCipher p = unexpected (show p) (Just "change cipher")
expectFinish (Finished _) = return RecvStateDone
expectFinish p = unexpected (show p) (Just "Handshake Finished")
recvServerHello sentExts = runRecvState ctx (RecvStateHandshake $ onServerHello sentExts)
2013-07-28 14:07:06 +00:00
-- | onServerHello process the ServerHello message on the client.
--
-- 1) check the version chosen by the server is one allowed by parameters.
-- 2) check that our compression and cipher algorithms are part of the list we sent
-- 3) check extensions received are part of the one we sent
-- 4) process the session parameter to see if the server want to start a new session or can resume
-- 5) process NPN extension
-- 6) if no resume switch to processCertificate SM or in resume switch to expectChangeCipher
--
2013-07-10 06:20:58 +00:00
onServerHello :: MonadIO m => [ExtensionID] -> Handshake -> m (RecvState m)
onServerHello sentExts sh@(ServerHello rver _ serverSession cipher compression exts) = do
2013-07-10 06:20:58 +00:00
when (rver == SSL2) $ throwCore $ Error_Protocol ("ssl2 is not supported", True, ProtocolVersion)
case find ((==) rver) allowedvers of
Nothing -> throwCore $ Error_Protocol ("version " ++ show rver ++ "is not supported", True, ProtocolVersion)
Just _ -> usingState_ ctx $ setVersion rver
-- find the compression and cipher methods that the server want to use.
case (find ((==) cipher . cipherID) ciphers, find ((==) compression . compressionID) compressions) of
(Nothing,_) -> throwCore $ Error_Protocol ("no cipher in common with the server", True, HandshakeFailure)
(_,Nothing) -> throwCore $ Error_Protocol ("no compression in common with the server", True, HandshakeFailure)
(Just cipherAlg, Just compressAlg) ->
usingHState ctx $ setPendingAlgs cipherAlg compressAlg
2013-07-10 06:20:58 +00:00
-- intersect sent extensions in client and the received extensions from server.
-- if server returns extensions that we didn't request, fail.
when (not $ null $ filter (not . flip elem sentExts . fst) exts) $
throwCore $ Error_Protocol ("spurious extensions received", True, UnsupportedExtension)
let resumingSession =
case clientWantSessionResume cparams of
Just (sessionId, sessionData) -> if serverSession == Session (Just sessionId) then Just sessionData else Nothing
Nothing -> Nothing
usingState_ ctx $ setSession serverSession (isJust resumingSession)
usingState_ ctx $ processServerHello sh
case extensionDecode False `fmap` (lookup extensionID_NextProtocolNegotiation exts) of
Just (Just (NextProtocolNegotiation protos)) -> usingState_ ctx $ do
setExtensionNPN True
setServerNextProtocolSuggest protos
_ -> return ()
case resumingSession of
Nothing -> return $ RecvStateHandshake processCertificate
Just sessionData -> do
usingHState ctx (setMasterSecret rver ClientRole $ sessionSecret sessionData)
2013-07-10 06:20:58 +00:00
return $ RecvStateNext expectChangeCipher
onServerHello _ p = unexpected (show p) (Just "server hello")
processCertificate :: MonadIO m => Handshake -> m (RecvState m)
processCertificate (Certificates certs) = do
usage <- liftIO $ E.catch (onCertificatesRecv params certs) rejectOnException
case usage of
CertificateUsageAccept -> return ()
CertificateUsageReject reason -> certificateRejected reason
return $ RecvStateHandshake processServerKeyExchange
processCertificate p = processServerKeyExchange p
processServerKeyExchange :: MonadIO m => Handshake -> m (RecvState m)
processServerKeyExchange (ServerKeyXchg _) = return $ RecvStateHandshake processCertificateRequest
processServerKeyExchange p = processCertificateRequest p
processCertificateRequest :: MonadIO m => Handshake -> m (RecvState m)
processCertificateRequest (CertRequest cTypes sigAlgs dNames) = do
-- When the server requests a client
-- certificate, we simply store the
-- information for later.
--
2013-07-20 06:18:16 +00:00
usingHState ctx $ setClientCertRequest (cTypes, sigAlgs, dNames)
2013-07-10 06:20:58 +00:00
return $ RecvStateHandshake processServerHelloDone
processCertificateRequest p = processServerHelloDone p
processServerHelloDone ServerHelloDone = return RecvStateDone
processServerHelloDone p = unexpected (show p) (Just "server hello data")
2012-08-18 22:05:56 +00:00
-- | send client Data after receiving all server data (hello/certificates/key).
--
-- -> [certificate]
-- -> client key exchange
-- -> [cert verify]
sendClientData :: MonadIO m => ClientParams -> Context -> m ()
sendClientData cparams ctx = sendCertificate >> sendClientKeyXchg >> sendCertificateVerify
2013-07-10 06:20:58 +00:00
where
-- When the server requests a client certificate, we
-- fetch a certificate chain from the callback in the
-- client parameters and send it to the server.
-- Additionally, we store the private key associated
-- with the first certificate in the chain for later
-- use.
--
sendCertificate = do
2013-07-20 06:18:16 +00:00
certRequested <- usingHState ctx getClientCertRequest
2013-07-10 06:20:58 +00:00
case certRequested of
Nothing ->
return ()
Just req -> do
certChain <- liftIO $ onCertificateRequest cparams req `E.catch`
throwMiscErrorOnException "certificate request callback failed"
2013-07-20 06:18:16 +00:00
usingHState ctx $ setClientCertSent False
2013-07-10 06:20:58 +00:00
case certChain of
Nothing -> sendPacket ctx $ Handshake [Certificates (CertificateChain [])]
Just (CertificateChain [], _) -> sendPacket ctx $ Handshake [Certificates (CertificateChain [])]
Just (cc@(CertificateChain (c:_)), pk) -> do
case certPubKey $ getCertificate c of
PubKeyRSA _ -> return ()
_ -> throwCore $ Error_Protocol ("no supported certificate type", True, HandshakeFailure)
usingHState ctx $ setClientPrivateKey pk
2013-07-20 06:18:16 +00:00
usingHState ctx $ setClientCertSent True
2013-07-10 06:20:58 +00:00
sendPacket ctx $ Handshake [Certificates cc]
sendClientKeyXchg = do
encryptedPreMaster <- usingState_ ctx $ do
2013-07-13 07:03:25 +00:00
xver <- getVersion
prerand <- genRandom 46
2013-07-10 06:20:58 +00:00
let premaster = encodePreMasterSecret xver prerand
withHandshakeM $ setMasterSecretFromPre xver ClientRole premaster
2013-07-10 06:20:58 +00:00
-- SSL3 implementation generally forget this length field since it's redundant,
-- however TLS10 make it clear that the length field need to be present.
e <- encryptRSA premaster
let extra = if xver < TLS10
then B.empty
else encodeWord16 $ fromIntegral $ B.length e
return $ extra `B.append` e
sendPacket ctx $ Handshake [ClientKeyXchg encryptedPreMaster]
-- In order to send a proper certificate verify message,
-- we have to do the following:
--
-- 1. Determine which signing algorithm(s) the server supports
-- (we currently only support RSA).
-- 2. Get the current handshake hash from the handshake state.
-- 3. Sign the handshake hash
-- 4. Send it to the server.
--
sendCertificateVerify = do
usedVersion <- usingState_ ctx getVersion
2013-07-10 06:20:58 +00:00
-- Only send a certificate verify message when we
-- have sent a non-empty list of certificates.
--
2013-07-20 06:18:16 +00:00
certSent <- usingHState ctx $ getClientCertSent
2013-07-10 06:20:58 +00:00
case certSent of
2013-07-20 06:18:16 +00:00
True -> do
2013-07-10 06:20:58 +00:00
-- Fetch all handshake messages up to now.
msgs <- usingHState ctx $ B.concat <$> getHandshakeMessages
2013-07-10 06:20:58 +00:00
case usedVersion of
SSL3 -> do
2013-07-23 05:51:44 +00:00
Just masterSecret <- usingHState ctx $ gets hstMasterSecret
2013-07-10 06:20:58 +00:00
let digest = generateCertificateVerify_SSL masterSecret (hashUpdate (hashInit hashMD5SHA1) msgs)
hsh = HashDescr id id
2013-07-10 06:20:58 +00:00
sigDig <- usingState_ ctx $ signRSA hsh digest
sendPacket ctx $ Handshake [CertVerify Nothing (CertVerifyData sigDig)]
2013-07-10 06:20:58 +00:00
x | x == TLS10 || x == TLS11 -> do
let hashf bs = hashFinal (hashUpdate (hashInit hashMD5SHA1) bs)
hsh = HashDescr hashf id
2013-07-10 06:20:58 +00:00
sigDig <- usingState_ ctx $ signRSA hsh msgs
sendPacket ctx $ Handshake [CertVerify Nothing (CertVerifyData sigDig)]
2013-07-10 06:20:58 +00:00
_ -> do
2013-07-20 06:18:16 +00:00
Just (_, Just hashSigs, _) <- usingHState ctx $ getClientCertRequest
2013-07-10 06:20:58 +00:00
let suppHashSigs = pHashSignatures $ ctxParams ctx
hashSigs' = filter (\ a -> a `elem` hashSigs) suppHashSigs
2013-07-10 06:20:58 +00:00
when (null hashSigs') $ do
throwCore $ Error_Protocol ("no hash/signature algorithms in common with the server", True, HandshakeFailure)
2013-07-10 06:20:58 +00:00
let hashSig = head hashSigs'
hsh <- getHashAndASN1 hashSig
2013-07-10 06:20:58 +00:00
sigDig <- usingState_ ctx $ signRSA hsh msgs
2013-07-10 06:20:58 +00:00
sendPacket ctx $ Handshake [CertVerify (Just hashSig) (CertVerifyData sigDig)]
2013-07-10 06:20:58 +00:00
_ -> return ()
2012-08-18 22:05:56 +00:00
2013-07-24 05:50:56 +00:00
processServerHello :: Handshake -> TLSSt ()
processServerHello (ServerHello sver ran _ _ _ ex) = do
-- FIXME notify the user to take action if the extension requested is missing
-- secreneg <- getSecureRenegotiation
-- when (secreneg && (isNothing $ lookup 0xff01 ex)) $ ...
mapM_ processServerExtension ex
withHandshakeM $ setServerRandom ran
setVersion sver
where processServerExtension (0xff01, content) = do
cv <- getVerifiedData ClientRole
sv <- getVerifiedData ServerRole
let bs = extensionEncode (SecureRenegotiation cv $ Just sv)
unless (bs `bytesEq` content) $ throwError $ Error_Protocol ("server secure renegotiation data not matching", True, HandshakeFailure)
return ()
processServerExtension _ = return ()
processServerHello _ = error "processServerHello called on wrong type"
2012-08-18 22:05:56 +00:00
throwMiscErrorOnException :: MonadIO m => String -> SomeException -> m a
throwMiscErrorOnException msg e =
2013-07-10 06:20:58 +00:00
throwCore $ Error_Misc $ msg ++ ": " ++ show e