2012-04-27 06:28:17 +00:00
|
|
|
{-# LANGUAGE DeriveDataTypeable, OverloadedStrings #-}
|
2012-04-27 06:29:35 +00:00
|
|
|
-- |
|
|
|
|
-- Module : Network.TLS.Handshake
|
|
|
|
-- License : BSD-style
|
|
|
|
-- Maintainer : Vincent Hanquez <vincent@snarc.org>
|
|
|
|
-- Stability : experimental
|
|
|
|
-- Portability : unknown
|
|
|
|
--
|
2012-04-27 06:28:17 +00:00
|
|
|
module Network.TLS.Handshake
|
|
|
|
( handshake
|
|
|
|
, handshakeServerWith
|
|
|
|
, handshakeClient
|
|
|
|
, HandshakeFailed(..)
|
|
|
|
) where
|
|
|
|
|
2012-07-17 15:33:11 +00:00
|
|
|
import Text.Printf
|
2012-04-27 06:28:17 +00:00
|
|
|
import Network.TLS.Context
|
2012-07-12 08:02:10 +00:00
|
|
|
import Network.TLS.Session
|
2012-04-27 06:28:17 +00:00
|
|
|
import Network.TLS.Struct
|
|
|
|
import Network.TLS.Cipher
|
|
|
|
import Network.TLS.Compression
|
|
|
|
import Network.TLS.Packet
|
2012-05-14 05:39:20 +00:00
|
|
|
import Network.TLS.Extension
|
2012-04-27 06:28:17 +00:00
|
|
|
import Network.TLS.IO
|
|
|
|
import Network.TLS.State hiding (getNegotiatedProtocol)
|
|
|
|
import Network.TLS.Sending
|
|
|
|
import Network.TLS.Receiving
|
|
|
|
import Network.TLS.Measurement
|
|
|
|
import Network.TLS.Wire (encodeWord16)
|
|
|
|
import Data.Maybe
|
|
|
|
import Data.Data
|
2012-07-17 15:33:11 +00:00
|
|
|
import Data.List (intersect, find, intercalate)
|
2012-04-27 06:28:17 +00:00
|
|
|
import qualified Data.ByteString as B
|
|
|
|
import Data.ByteString.Char8 ()
|
|
|
|
|
2012-07-14 14:50:48 +00:00
|
|
|
import Data.Certificate.X509(X509, certSubjectDN, x509Cert)
|
2012-07-13 20:04:23 +00:00
|
|
|
|
2012-04-27 06:28:17 +00:00
|
|
|
import Control.Applicative ((<$>))
|
|
|
|
import Control.Monad.State
|
|
|
|
import Control.Exception (throwIO, Exception(), fromException, catch, SomeException)
|
|
|
|
import Prelude hiding (catch)
|
|
|
|
|
|
|
|
data HandshakeFailed = HandshakeFailed TLSError
|
|
|
|
deriving (Show,Eq,Typeable)
|
|
|
|
|
|
|
|
instance Exception HandshakeFailed
|
|
|
|
|
|
|
|
handshakeFailed :: TLSError -> IO ()
|
|
|
|
handshakeFailed err = throwIO $ HandshakeFailed err
|
|
|
|
|
|
|
|
recvPacketHandshake :: MonadIO m => Context -> m [Handshake]
|
|
|
|
recvPacketHandshake ctx = do
|
|
|
|
pkts <- recvPacket ctx
|
|
|
|
case pkts of
|
|
|
|
Right (Handshake l) -> return l
|
|
|
|
Right x -> fail ("unexpected type received. expecting handshake and got: " ++ show x)
|
|
|
|
Left err -> throwCore err
|
|
|
|
|
|
|
|
errorToAlert :: TLSError -> Packet
|
|
|
|
errorToAlert (Error_Protocol (_, _, ad)) = Alert [(AlertLevel_Fatal, ad)]
|
|
|
|
errorToAlert _ = Alert [(AlertLevel_Fatal, InternalError)]
|
|
|
|
|
|
|
|
data RecvState m =
|
|
|
|
RecvStateNext (Packet -> m (RecvState m))
|
|
|
|
| RecvStateHandshake (Handshake -> m (RecvState m))
|
|
|
|
| RecvStateDone
|
|
|
|
|
|
|
|
runRecvState :: MonadIO m => Context -> RecvState m -> m ()
|
|
|
|
runRecvState _ (RecvStateDone) = return ()
|
|
|
|
runRecvState ctx (RecvStateNext f) = recvPacket ctx >>= either throwCore f >>= runRecvState ctx
|
|
|
|
runRecvState ctx iniState = recvPacketHandshake ctx >>= loop iniState >>= runRecvState ctx
|
|
|
|
where
|
|
|
|
loop :: MonadIO m => RecvState m -> [Handshake] -> m (RecvState m)
|
|
|
|
loop recvState [] = return recvState
|
|
|
|
loop (RecvStateHandshake f) (x:xs) = do
|
|
|
|
nstate <- f x
|
|
|
|
usingState_ ctx $ processHandshake x
|
|
|
|
loop nstate xs
|
|
|
|
loop _ _ = unexpected "spurious handshake" Nothing
|
|
|
|
|
|
|
|
sendChangeCipherAndFinish :: MonadIO m => Context -> Bool -> m ()
|
|
|
|
sendChangeCipherAndFinish ctx isClient = do
|
|
|
|
sendPacket ctx ChangeCipherSpec
|
|
|
|
when isClient $ do
|
|
|
|
suggest <- usingState_ ctx $ getServerNextProtocolSuggest
|
|
|
|
case (onNPNServerSuggest (ctxParams ctx), suggest) of
|
|
|
|
-- client offered, server picked up. send NPN handshake.
|
|
|
|
(Just io, Just protos) -> do proto <- liftIO $ io protos
|
2012-05-14 05:35:55 +00:00
|
|
|
sendPacket ctx (Handshake [HsNextProtocolNegotiation proto])
|
2012-04-27 06:28:17 +00:00
|
|
|
usingState_ ctx $ setNegotiatedProtocol proto
|
|
|
|
-- client offered, server didn't pick up. do nothing.
|
|
|
|
(Just _, Nothing) -> return ()
|
|
|
|
-- client didn't offer. do nothing.
|
|
|
|
(Nothing, _) -> return ()
|
|
|
|
liftIO $ contextFlush ctx
|
2012-07-17 15:33:11 +00:00
|
|
|
|
2012-07-17 15:42:12 +00:00
|
|
|
-- msgs <- usingState_ ctx $ getHandshakeMessages
|
|
|
|
-- liftIO $ putStrLn $ formatHandshakeMessages msgs
|
|
|
|
|
2012-04-27 06:28:17 +00:00
|
|
|
cf <- usingState_ ctx $ getHandshakeDigest isClient
|
|
|
|
sendPacket ctx (Handshake [Finished cf])
|
|
|
|
liftIO $ contextFlush ctx
|
|
|
|
|
|
|
|
recvChangeCipherAndFinish :: MonadIO m => Context -> m ()
|
|
|
|
recvChangeCipherAndFinish ctx = runRecvState ctx (RecvStateNext expectChangeCipher)
|
|
|
|
where
|
2012-07-17 15:42:12 +00:00
|
|
|
expectChangeCipher ChangeCipherSpec = do
|
|
|
|
msgs <- usingState_ ctx $ getHandshakeMessages
|
|
|
|
liftIO $ putStrLn $ formatHandshakeMessages msgs
|
|
|
|
|
|
|
|
return $ RecvStateHandshake expectFinish
|
2012-04-27 06:28:17 +00:00
|
|
|
expectChangeCipher p = unexpected (show p) (Just "change cipher")
|
|
|
|
expectFinish (Finished _) = return RecvStateDone
|
|
|
|
expectFinish p = unexpected (show p) (Just "Handshake Finished")
|
|
|
|
|
|
|
|
unexpected :: MonadIO m => String -> Maybe [Char] -> m a
|
|
|
|
unexpected msg expected = throwCore $ Error_Packet_unexpected msg (maybe "" (" expected: " ++) expected)
|
|
|
|
|
|
|
|
newSession :: MonadIO m => Context -> m Session
|
|
|
|
newSession ctx
|
|
|
|
| pUseSession $ ctxParams ctx = getStateRNG ctx 32 >>= return . Session . Just
|
|
|
|
| otherwise = return $ Session Nothing
|
|
|
|
|
|
|
|
|
|
|
|
-- | when a new handshake is done, wrap up & clean up.
|
|
|
|
handshakeTerminate :: MonadIO m => Context -> m ()
|
|
|
|
handshakeTerminate ctx = do
|
|
|
|
session <- usingState_ ctx getSession
|
|
|
|
-- only callback the session established if we have a session
|
|
|
|
case session of
|
|
|
|
Session (Just sessionId) -> do
|
|
|
|
sessionData <- usingState_ ctx getSessionData
|
2012-07-12 07:59:59 +00:00
|
|
|
withSessionManager (ctxParams ctx) (\s -> liftIO $ sessionEstablish s sessionId (fromJust sessionData))
|
2012-04-27 06:28:17 +00:00
|
|
|
_ -> return ()
|
|
|
|
-- forget all handshake data now and reset bytes counters.
|
|
|
|
usingState_ ctx endHandshake
|
|
|
|
updateMeasure ctx resetBytesCounters
|
|
|
|
-- mark the secure connection up and running.
|
|
|
|
setEstablished ctx True
|
|
|
|
return ()
|
|
|
|
|
|
|
|
-- client part of handshake. send a bunch of handshake of client
|
|
|
|
-- values intertwined with response from the server.
|
2012-07-12 08:02:10 +00:00
|
|
|
handshakeClient :: MonadIO m => ClientParams -> Context -> m ()
|
|
|
|
handshakeClient cparams ctx = do
|
2012-04-27 06:28:17 +00:00
|
|
|
updateMeasure ctx incrementNbHandshakes
|
|
|
|
sendClientHello
|
|
|
|
recvServerHello
|
|
|
|
sessionResuming <- usingState_ ctx isSessionResuming
|
|
|
|
if sessionResuming
|
|
|
|
then sendChangeCipherAndFinish ctx True
|
|
|
|
else do
|
|
|
|
sendCertificate >> sendClientKeyXchg >> sendCertificateVerify
|
|
|
|
sendChangeCipherAndFinish ctx True
|
|
|
|
recvChangeCipherAndFinish ctx
|
|
|
|
handshakeTerminate ctx
|
|
|
|
where
|
|
|
|
params = ctxParams ctx
|
|
|
|
ver = pConnectVersion params
|
|
|
|
allowedvers = pAllowedVersions params
|
|
|
|
ciphers = pCiphers params
|
|
|
|
compressions = pCompressions params
|
2012-05-14 05:39:20 +00:00
|
|
|
getExtensions = sequence [secureReneg,npnExtention] >>= return . catMaybes
|
|
|
|
|
|
|
|
toExtensionRaw :: Extension e => e -> ExtensionRaw
|
|
|
|
toExtensionRaw ext = (extensionID ext, extensionEncode ext)
|
|
|
|
|
2012-04-27 06:28:17 +00:00
|
|
|
secureReneg =
|
|
|
|
if pUseSecureRenegotiation params
|
2012-05-14 05:39:20 +00:00
|
|
|
then usingState_ ctx (getVerifiedData True) >>= \vd -> return $ Just $ toExtensionRaw $ SecureRenegotiation vd Nothing
|
2012-04-27 06:28:17 +00:00
|
|
|
else return Nothing
|
|
|
|
npnExtention = if isJust $ onNPNServerSuggest params
|
2012-05-14 05:39:20 +00:00
|
|
|
then return $ Just $ toExtensionRaw $ NextProtocolNegotiation []
|
2012-04-27 06:28:17 +00:00
|
|
|
else return Nothing
|
|
|
|
sendClientHello = do
|
|
|
|
crand <- getStateRNG ctx 32 >>= return . ClientRandom
|
2012-07-12 08:02:10 +00:00
|
|
|
let clientSession = Session . maybe Nothing (Just . fst) $ clientWantSessionResume cparams
|
2012-04-27 06:28:17 +00:00
|
|
|
extensions <- getExtensions
|
|
|
|
usingState_ ctx (startHandshakeClient ver crand)
|
|
|
|
sendPacket ctx $ Handshake
|
|
|
|
[ ClientHello ver crand clientSession (map cipherID ciphers)
|
|
|
|
(map compressionID compressions) 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")
|
|
|
|
|
2012-07-13 20:04:23 +00:00
|
|
|
-- When the server requests a client certificate, we
|
|
|
|
-- fetch a certificate chain from the callback in the
|
2012-07-14 14:49:46 +00:00
|
|
|
-- 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.
|
2012-07-13 20:04:23 +00:00
|
|
|
--
|
2012-04-27 06:28:17 +00:00
|
|
|
sendCertificate = do
|
2012-07-13 20:04:23 +00:00
|
|
|
certRequested <- usingState_ ctx getClientCertRequest
|
|
|
|
case certRequested of
|
|
|
|
Nothing ->
|
|
|
|
return ()
|
2012-07-14 14:49:46 +00:00
|
|
|
|
2012-07-13 20:04:23 +00:00
|
|
|
Just req ->
|
2012-07-14 14:49:46 +00:00
|
|
|
|
2012-07-16 12:36:44 +00:00
|
|
|
case roleParams $ ctxParams ctx of
|
|
|
|
Server{} ->
|
|
|
|
throwCore $ Error_Misc "not allowed in server context"
|
2012-07-13 20:04:23 +00:00
|
|
|
-- FIXME: I interpret section 7.4.2 of
|
|
|
|
-- RFC 2246 that a client may send an
|
|
|
|
-- empty list if it does not have a
|
|
|
|
-- matching certificate.
|
|
|
|
--
|
|
|
|
-- When the user has not configured
|
|
|
|
-- client certificates, we do exactly
|
|
|
|
-- that.
|
2012-07-14 14:49:46 +00:00
|
|
|
--
|
2012-07-16 12:36:44 +00:00
|
|
|
-- sendPacket ctx $ Handshake [Certificates []]
|
2012-07-14 14:49:46 +00:00
|
|
|
|
2012-07-16 12:36:44 +00:00
|
|
|
Client cp -> do
|
2012-07-13 20:04:23 +00:00
|
|
|
-- FIXME: What shall we do when the
|
|
|
|
-- callback throws an exception?
|
2012-07-14 14:49:46 +00:00
|
|
|
--
|
2012-07-16 12:36:44 +00:00
|
|
|
certChain <- liftIO $ onCertificateRequest cp req `catch`
|
2012-07-13 20:04:23 +00:00
|
|
|
throwMiscErrorOnException "certificate request callback failed"
|
2012-07-14 14:49:46 +00:00
|
|
|
|
|
|
|
-- FIXME: Currently, when the first
|
|
|
|
-- client certificate has no
|
|
|
|
-- associated private key (or when the
|
|
|
|
-- application offered no
|
|
|
|
-- certificates), we simply do not
|
|
|
|
-- install the key for later use.
|
|
|
|
-- This will lead to an error later
|
|
|
|
-- on, but it would propbably better
|
|
|
|
-- to fail explicitly.
|
|
|
|
--
|
2012-07-13 20:04:23 +00:00
|
|
|
case certChain of
|
|
|
|
(_, Just pk) : _ ->
|
|
|
|
usingState_ ctx $ setClientPrivateKey pk
|
|
|
|
_ ->
|
|
|
|
return ()
|
2012-07-14 14:49:46 +00:00
|
|
|
|
|
|
|
-- FIXME: Check that we can sign with
|
|
|
|
-- the provided certificate.
|
|
|
|
|
|
|
|
-- FIXME: Check that the certificate
|
|
|
|
-- matches the types requeted by the
|
|
|
|
-- server.
|
|
|
|
|
2012-07-13 20:04:23 +00:00
|
|
|
usingState_ ctx $ setClientCertSent (not $ null certChain)
|
|
|
|
sendPacket ctx $ Handshake [Certificates $ map fst certChain]
|
|
|
|
|
|
|
|
-- 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
|
2012-07-16 12:40:37 +00:00
|
|
|
-- Only send a certificate verify message when we
|
|
|
|
-- have sent a non-empty list of certificates.
|
|
|
|
--
|
2012-07-16 12:36:44 +00:00
|
|
|
certSent <- usingState_ ctx $ getClientCertSent
|
|
|
|
when (isJust certSent && fromJust certSent) $ do
|
2012-07-16 12:40:37 +00:00
|
|
|
|
2012-07-14 14:49:46 +00:00
|
|
|
-- Determine certificate request parameters.
|
|
|
|
-- When no certicicate was requested, do
|
|
|
|
-- nothing.
|
|
|
|
--
|
2012-07-13 20:04:23 +00:00
|
|
|
certRequested <- usingState_ ctx getClientCertRequest
|
|
|
|
case certRequested of
|
|
|
|
Nothing ->
|
|
|
|
return ()
|
|
|
|
|
|
|
|
Just _ -> do
|
|
|
|
withClientCertClient ctx $ \ _ -> do
|
|
|
|
-- Fetch the current handshake hash.
|
|
|
|
dig <- usingState_ ctx $ getCertVerifyDigest
|
2012-07-14 14:49:46 +00:00
|
|
|
|
|
|
|
-- FIXME: Need to chek whether the
|
|
|
|
-- server supports RSA signing.
|
|
|
|
|
2012-07-13 20:04:23 +00:00
|
|
|
-- Sign the hash.
|
2012-07-14 14:49:46 +00:00
|
|
|
--
|
|
|
|
-- FIXME: Dows not work yet. RSA
|
|
|
|
-- signing is not used correctly yet.
|
|
|
|
--
|
2012-07-13 20:04:23 +00:00
|
|
|
sigDig <- usingState_ ctx $ signRSA dig
|
|
|
|
|
|
|
|
-- Send the digest
|
|
|
|
sendPacket ctx $ Handshake [CertVerify sigDig]
|
2012-04-27 06:28:17 +00:00
|
|
|
|
|
|
|
recvServerHello = runRecvState ctx (RecvStateHandshake onServerHello)
|
|
|
|
|
|
|
|
onServerHello :: MonadIO m => Handshake -> m (RecvState m)
|
|
|
|
onServerHello sh@(ServerHello rver _ serverSession cipher _ exts) = do
|
|
|
|
when (rver == SSL2) $ throwCore $ Error_Protocol ("ssl2 is not supported", True, ProtocolVersion)
|
|
|
|
case find ((==) rver) allowedvers of
|
|
|
|
Nothing -> throwCore $ Error_Protocol ("version " ++ show ver ++ "is not supported", True, ProtocolVersion)
|
|
|
|
Just _ -> usingState_ ctx $ setVersion ver
|
|
|
|
case find ((==) cipher . cipherID) ciphers of
|
|
|
|
Nothing -> throwCore $ Error_Protocol ("no cipher in common with the server", True, HandshakeFailure)
|
|
|
|
Just c -> usingState_ ctx $ setCipher c
|
|
|
|
|
2012-07-12 08:02:10 +00:00
|
|
|
let resumingSession = case clientWantSessionResume cparams of
|
2012-04-27 06:28:17 +00:00
|
|
|
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
|
2012-05-14 05:39:20 +00:00
|
|
|
|
2012-07-05 04:36:28 +00:00
|
|
|
case extensionDecode False `fmap` (lookup extensionID_NextProtocolNegotiation exts) of
|
2012-05-14 05:39:20 +00:00
|
|
|
Just (Just (NextProtocolNegotiation protos)) -> usingState_ ctx $ do
|
|
|
|
setExtensionNPN True
|
|
|
|
setServerNextProtocolSuggest protos
|
|
|
|
_ -> return ()
|
|
|
|
|
2012-04-27 06:28:17 +00:00
|
|
|
case resumingSession of
|
|
|
|
Nothing -> return $ RecvStateHandshake processCertificate
|
|
|
|
Just sessionData -> do
|
|
|
|
usingState_ ctx (setMasterSecret $ sessionSecret sessionData)
|
|
|
|
return $ RecvStateNext expectChangeCipher
|
|
|
|
onServerHello p = unexpected (show p) (Just "server hello")
|
|
|
|
|
|
|
|
processCertificate :: MonadIO m => Handshake -> m (RecvState m)
|
|
|
|
processCertificate (Certificates certs) = do
|
|
|
|
usage <- liftIO $ catch (onCertificatesRecv params $ certs) rejectOnException
|
|
|
|
case usage of
|
|
|
|
CertificateUsageAccept -> return ()
|
|
|
|
CertificateUsageReject reason -> certificateRejected reason
|
|
|
|
return $ RecvStateHandshake processServerKeyExchange
|
2012-07-14 14:49:46 +00:00
|
|
|
|
2012-04-27 06:28:17 +00:00
|
|
|
processCertificate p = processServerKeyExchange p
|
|
|
|
|
|
|
|
processServerKeyExchange :: MonadIO m => Handshake -> m (RecvState m)
|
|
|
|
processServerKeyExchange (ServerKeyXchg _) = return $ RecvStateHandshake processCertificateRequest
|
|
|
|
processServerKeyExchange p = processCertificateRequest p
|
|
|
|
|
2012-07-13 20:04:23 +00:00
|
|
|
processCertificateRequest :: MonadIO m => Handshake -> m (RecvState m)
|
|
|
|
processCertificateRequest (CertRequest cTypes sigAlgs dNames) = do
|
2012-07-14 14:49:46 +00:00
|
|
|
-- When the server requests a client
|
|
|
|
-- certificate, we simply store the
|
|
|
|
-- information for later.
|
|
|
|
--
|
2012-07-13 20:04:23 +00:00
|
|
|
usingState_ ctx $ setClientCertRequest (cTypes, sigAlgs, dNames)
|
2012-04-27 06:28:17 +00:00
|
|
|
return $ RecvStateHandshake processServerHelloDone
|
|
|
|
processCertificateRequest p = processServerHelloDone p
|
|
|
|
|
|
|
|
processServerHelloDone ServerHelloDone = return RecvStateDone
|
|
|
|
processServerHelloDone p = unexpected (show p) (Just "server hello data")
|
|
|
|
|
|
|
|
sendClientKeyXchg = do
|
|
|
|
encryptedPreMaster <- usingState_ ctx $ do
|
|
|
|
xver <- stVersion <$> get
|
|
|
|
prerand <- genTLSRandom 46
|
|
|
|
let premaster = encodePreMasterSecret xver prerand
|
|
|
|
setMasterSecretFromPre premaster
|
|
|
|
|
|
|
|
-- 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]
|
|
|
|
|
2012-07-13 19:48:37 +00:00
|
|
|
-- on certificate reject, throw an exception with the proper protocol alert error.
|
|
|
|
certificateRejected :: MonadIO m => CertificateRejectReason -> m a
|
|
|
|
certificateRejected CertificateRejectRevoked =
|
|
|
|
throwCore $ Error_Protocol ("certificate is revoked", True, CertificateRevoked)
|
|
|
|
certificateRejected CertificateRejectExpired =
|
|
|
|
throwCore $ Error_Protocol ("certificate has expired", True, CertificateExpired)
|
|
|
|
certificateRejected CertificateRejectUnknownCA =
|
|
|
|
throwCore $ Error_Protocol ("certificate has unknown CA", True, UnknownCa)
|
|
|
|
certificateRejected (CertificateRejectOther s) =
|
|
|
|
throwCore $ Error_Protocol ("certificate rejected: " ++ s, True, CertificateUnknown)
|
|
|
|
|
|
|
|
rejectOnException :: SomeException -> IO TLSCertificateUsage
|
|
|
|
rejectOnException e = return $ CertificateUsageReject $ CertificateRejectOther $ show e
|
2012-04-27 06:28:17 +00:00
|
|
|
|
2012-07-12 08:02:10 +00:00
|
|
|
handshakeServerWith :: MonadIO m => ServerParams -> Context -> Handshake -> m ()
|
|
|
|
handshakeServerWith sparams ctx clientHello@(ClientHello ver _ clientSession ciphers compressions exts) = do
|
2012-04-27 06:28:17 +00:00
|
|
|
-- check if policy allow this new handshake to happens
|
|
|
|
handshakeAuthorized <- withMeasure ctx (onHandshake $ ctxParams ctx)
|
|
|
|
unless handshakeAuthorized (throwCore $ Error_HandshakePolicy "server: handshake denied")
|
|
|
|
updateMeasure ctx incrementNbHandshakes
|
|
|
|
|
|
|
|
-- Handle Client hello
|
|
|
|
usingState_ ctx $ processHandshake clientHello
|
|
|
|
when (ver == SSL2) $ throwCore $ Error_Protocol ("ssl2 is not supported", True, ProtocolVersion)
|
|
|
|
when (not $ elem ver (pAllowedVersions params)) $
|
|
|
|
throwCore $ Error_Protocol ("version " ++ show ver ++ "is not supported", True, ProtocolVersion)
|
|
|
|
when (commonCiphers == []) $
|
|
|
|
throwCore $ Error_Protocol ("no cipher in common with the client", True, HandshakeFailure)
|
|
|
|
when (null commonCompressions) $
|
|
|
|
throwCore $ Error_Protocol ("no compression in common with the client", True, HandshakeFailure)
|
|
|
|
usingState_ ctx $ modify (\st -> st
|
|
|
|
{ stVersion = ver
|
|
|
|
, stCipher = Just usedCipher
|
|
|
|
, stCompression = usedCompression
|
|
|
|
})
|
|
|
|
|
|
|
|
resumeSessionData <- case clientSession of
|
2012-07-12 07:59:59 +00:00
|
|
|
(Session (Just clientSessionId)) -> withSessionManager params (\s -> liftIO $ sessionResume s clientSessionId)
|
2012-04-27 06:28:17 +00:00
|
|
|
(Session Nothing) -> return Nothing
|
|
|
|
case resumeSessionData of
|
|
|
|
Nothing -> do
|
|
|
|
handshakeSendServerData
|
|
|
|
liftIO $ contextFlush ctx
|
|
|
|
|
|
|
|
-- Receive client info until client Finished.
|
|
|
|
recvClientData
|
|
|
|
sendChangeCipherAndFinish ctx False
|
|
|
|
Just sessionData -> do
|
|
|
|
usingState_ ctx (setSession clientSession True)
|
|
|
|
serverhello <- makeServerHello clientSession
|
|
|
|
sendPacket ctx $ Handshake [serverhello]
|
|
|
|
usingState_ ctx $ setMasterSecret $ sessionSecret sessionData
|
|
|
|
sendChangeCipherAndFinish ctx False
|
|
|
|
recvChangeCipherAndFinish ctx
|
|
|
|
handshakeTerminate ctx
|
|
|
|
where
|
|
|
|
params = ctxParams ctx
|
|
|
|
commonCiphers = intersect ciphers (map cipherID $ pCiphers params)
|
|
|
|
usedCipher = fromJust $ find (\c -> cipherID c == head commonCiphers) (pCiphers params)
|
|
|
|
commonCompressions = compressionIntersectID (pCompressions params) compressions
|
|
|
|
usedCompression = head commonCompressions
|
|
|
|
srvCerts = map fst $ pCertificates params
|
|
|
|
privKeys = map snd $ pCertificates params
|
|
|
|
needKeyXchg = cipherExchangeNeedMoreData $ cipherKeyExchange usedCipher
|
2012-07-05 04:36:28 +00:00
|
|
|
clientRequestedNPN = isJust $ lookup extensionID_NextProtocolNegotiation exts
|
2012-04-27 06:28:17 +00:00
|
|
|
|
|
|
|
---
|
|
|
|
recvClientData = runRecvState ctx (RecvStateHandshake processClientCertificate)
|
|
|
|
|
2012-07-14 14:49:46 +00:00
|
|
|
-- When the client sends a certificate, check whether
|
|
|
|
-- it is acceptable for the application.
|
|
|
|
--
|
2012-07-13 20:04:23 +00:00
|
|
|
processClientCertificate (Certificates certs) =
|
2012-07-14 14:49:46 +00:00
|
|
|
-- Note that the following call will throw an
|
|
|
|
-- exception when we did not request a certificate.
|
|
|
|
--
|
2012-07-13 20:04:23 +00:00
|
|
|
withClientCertServer ctx $ \ ccp -> do
|
2012-07-14 14:49:46 +00:00
|
|
|
-- Call application callback to see whether the
|
|
|
|
-- certificate chain is acceptable.
|
|
|
|
--
|
2012-07-13 20:04:23 +00:00
|
|
|
usage <- liftIO $ catch (onClientCertificate ccp certs) rejectOnException
|
|
|
|
case usage of
|
|
|
|
CertificateUsageAccept -> return ()
|
|
|
|
CertificateUsageReject reason -> certificateRejected reason
|
2012-07-14 14:49:46 +00:00
|
|
|
|
2012-07-16 12:40:37 +00:00
|
|
|
-- Remember cert chain for later use.
|
|
|
|
--
|
2012-07-16 12:36:44 +00:00
|
|
|
usingState_ ctx $ setClientCertChain certs
|
2012-07-14 14:49:46 +00:00
|
|
|
|
|
|
|
-- FIXME: We should check whether the certificate
|
|
|
|
-- matches our request and that we support
|
|
|
|
-- verifying with that certificate.
|
|
|
|
|
2012-07-13 20:04:23 +00:00
|
|
|
return $ RecvStateHandshake processClientKeyExchange
|
|
|
|
|
2012-07-14 14:49:46 +00:00
|
|
|
|
2012-04-27 06:28:17 +00:00
|
|
|
processClientCertificate p = processClientKeyExchange p
|
|
|
|
|
|
|
|
processClientKeyExchange (ClientKeyXchg _) = return $ RecvStateNext processCertificateVerify
|
|
|
|
processClientKeyExchange p = unexpected (show p) (Just "client key exchange")
|
|
|
|
|
2012-07-14 14:49:46 +00:00
|
|
|
-- Check whether the client correctly signed the handshake.
|
|
|
|
-- If not, ask the application on how to proceed.
|
|
|
|
--
|
2012-07-13 20:04:23 +00:00
|
|
|
processCertificateVerify (Handshake [CertVerify bs]) =
|
|
|
|
withClientCertServer ctx $ \ ccp -> do
|
|
|
|
dig <- usingState_ ctx $ getCertVerifyDigest
|
2012-07-14 14:49:46 +00:00
|
|
|
|
|
|
|
-- Verify the signature.
|
2012-07-13 20:04:23 +00:00
|
|
|
verif <- usingState_ ctx $ verifyRSA dig bs
|
2012-07-14 14:49:46 +00:00
|
|
|
|
2012-07-13 20:04:23 +00:00
|
|
|
case verif of
|
2012-07-16 12:36:44 +00:00
|
|
|
Right True -> do
|
2012-07-16 12:40:37 +00:00
|
|
|
-- When verification succeeds, commit the
|
|
|
|
-- client certificate chain to the context.
|
|
|
|
--
|
2012-07-16 12:36:44 +00:00
|
|
|
Just certs <- usingState_ ctx $ getClientCertChain
|
|
|
|
usingState_ ctx $ setClientCertificateChain certs
|
2012-07-13 20:04:23 +00:00
|
|
|
return ()
|
2012-07-14 14:49:46 +00:00
|
|
|
|
2012-07-13 20:04:23 +00:00
|
|
|
_ -> do
|
2012-07-14 14:49:46 +00:00
|
|
|
-- Either verification failed because of an
|
|
|
|
-- invalid format (with an error message), or
|
|
|
|
-- the signature is wrong. In either case,
|
|
|
|
-- ask the application -- if it wants to
|
|
|
|
-- proceed, we will do that.
|
|
|
|
--
|
2012-07-13 20:04:23 +00:00
|
|
|
let arg = case verif of Left err -> Just err; _ -> Nothing
|
|
|
|
res <- liftIO $ onUnverifiedClientCert ccp arg
|
2012-07-16 12:36:44 +00:00
|
|
|
if res
|
|
|
|
then do
|
2012-07-16 12:40:37 +00:00
|
|
|
-- When verification fails, but the
|
|
|
|
-- application callbacks accepts, we
|
|
|
|
-- also commit the client certificate
|
|
|
|
-- chain to the context.
|
|
|
|
--
|
2012-07-16 12:36:44 +00:00
|
|
|
Just certs <- usingState_ ctx $ getClientCertChain
|
|
|
|
usingState_ ctx $ setClientCertificateChain certs
|
|
|
|
else do
|
|
|
|
case verif of
|
|
|
|
Left err ->
|
|
|
|
throwCore $ Error_Protocol (show err, True, DecryptError)
|
|
|
|
_ ->
|
|
|
|
throwCore $ Error_Protocol ("verification failed", True, BadCertificate)
|
2012-07-13 20:04:23 +00:00
|
|
|
return $ RecvStateNext expectChangeCipher
|
|
|
|
|
2012-04-27 06:28:17 +00:00
|
|
|
processCertificateVerify p = expectChangeCipher p
|
|
|
|
|
|
|
|
expectChangeCipher ChangeCipherSpec = do npn <- usingState_ ctx getExtensionNPN
|
|
|
|
return $ RecvStateHandshake $ if npn
|
|
|
|
then expectNPN
|
|
|
|
else expectFinish
|
|
|
|
expectChangeCipher p = unexpected (show p) (Just "change cipher")
|
|
|
|
|
2012-05-14 05:35:55 +00:00
|
|
|
expectNPN (HsNextProtocolNegotiation _) = return $ RecvStateHandshake expectFinish
|
|
|
|
expectNPN p = unexpected (show p) (Just "Handshake NextProtocolNegotiation")
|
2012-04-27 06:28:17 +00:00
|
|
|
|
|
|
|
expectFinish (Finished _) = return RecvStateDone
|
|
|
|
expectFinish p = unexpected (show p) (Just "Handshake Finished")
|
|
|
|
---
|
|
|
|
|
|
|
|
makeServerHello session = do
|
|
|
|
srand <- getStateRNG ctx 32 >>= return . ServerRandom
|
|
|
|
case privKeys of
|
|
|
|
(Just privkey : _) -> usingState_ ctx $ setPrivateKey privkey
|
|
|
|
_ -> return () -- return a sensible error
|
|
|
|
|
|
|
|
-- in TLS12, we need to check as well the certificates we are sending if they have in the extension
|
|
|
|
-- the necessary bits set.
|
|
|
|
secReneg <- usingState_ ctx getSecureRenegotiation
|
|
|
|
secRengExt <- if secReneg
|
|
|
|
then do
|
|
|
|
vf <- usingState_ ctx $ do
|
|
|
|
cvf <- getVerifiedData True
|
|
|
|
svf <- getVerifiedData False
|
2012-05-14 05:39:20 +00:00
|
|
|
return $ extensionEncode (SecureRenegotiation cvf $ Just svf)
|
2012-04-27 06:28:17 +00:00
|
|
|
return [ (0xff01, vf) ]
|
|
|
|
else return []
|
|
|
|
nextProtocols <-
|
|
|
|
if clientRequestedNPN
|
|
|
|
then liftIO $ onSuggestNextProtocols params
|
|
|
|
else return Nothing
|
|
|
|
npnExt <- case nextProtocols of
|
|
|
|
Just protos -> do usingState_ ctx $ do setExtensionNPN True
|
|
|
|
setServerNextProtocolSuggest protos
|
2012-07-05 04:36:28 +00:00
|
|
|
return [ ( extensionID_NextProtocolNegotiation
|
|
|
|
, extensionEncode $ NextProtocolNegotiation protos) ]
|
2012-04-27 06:28:17 +00:00
|
|
|
Nothing -> return []
|
|
|
|
let extensions = secRengExt ++ npnExt
|
|
|
|
usingState_ ctx (setVersion ver >> setServerRandom srand)
|
|
|
|
return $ ServerHello ver srand session (cipherID usedCipher)
|
|
|
|
(compressionID usedCompression) extensions
|
|
|
|
|
|
|
|
handshakeSendServerData = do
|
|
|
|
serverSession <- newSession ctx
|
|
|
|
usingState_ ctx (setSession serverSession False)
|
|
|
|
serverhello <- makeServerHello serverSession
|
|
|
|
-- send ServerHello & Certificate & ServerKeyXchg & CertReq
|
|
|
|
sendPacket ctx $ Handshake [ serverhello, Certificates srvCerts ]
|
|
|
|
when needKeyXchg $ do
|
|
|
|
let skg = SKX_RSA Nothing
|
|
|
|
sendPacket ctx (Handshake [ServerKeyXchg skg])
|
2012-07-14 14:49:46 +00:00
|
|
|
|
2012-04-27 06:28:17 +00:00
|
|
|
-- FIXME we don't do this on a Anonymous server
|
2012-07-14 14:49:46 +00:00
|
|
|
|
|
|
|
-- When configured, send a certificate request
|
|
|
|
-- with the DNs of all confgure CA
|
|
|
|
-- certificates.
|
|
|
|
--
|
2012-07-16 12:36:44 +00:00
|
|
|
case roleParams $ ctxParams ctx of
|
|
|
|
Client{} ->
|
|
|
|
throwCore $ Error_Misc "not allowed in client context"
|
2012-07-14 14:49:46 +00:00
|
|
|
|
2012-07-16 12:36:44 +00:00
|
|
|
Server sp -> do
|
2012-07-13 20:04:23 +00:00
|
|
|
let certTypes = [ CertificateType_RSA_Sign ]
|
2012-07-16 12:36:44 +00:00
|
|
|
let creq = CertRequest certTypes Nothing (map extractCAname $ serverCACertificates sp)
|
2012-07-13 20:04:23 +00:00
|
|
|
sendPacket ctx (Handshake [creq])
|
2012-04-27 06:28:17 +00:00
|
|
|
-- Send HelloDone
|
|
|
|
sendPacket ctx (Handshake [ServerHelloDone])
|
|
|
|
|
2012-07-13 20:04:23 +00:00
|
|
|
extractCAname :: X509 -> DistinguishedName
|
2012-07-14 14:50:48 +00:00
|
|
|
extractCAname cert = DistinguishedName $ certSubjectDN (x509Cert cert)
|
2012-07-14 14:49:46 +00:00
|
|
|
|
2012-07-12 08:02:10 +00:00
|
|
|
handshakeServerWith _ _ _ = fail "unexpected handshake type received. expecting client hello"
|
2012-04-27 06:28:17 +00:00
|
|
|
|
|
|
|
-- after receiving a client hello, we need to redo a handshake
|
2012-07-12 08:02:10 +00:00
|
|
|
handshakeServer :: MonadIO m => ServerParams -> Context -> m ()
|
|
|
|
handshakeServer sparams ctx = do
|
2012-04-27 06:28:17 +00:00
|
|
|
hss <- recvPacketHandshake ctx
|
|
|
|
case hss of
|
2012-07-12 08:02:10 +00:00
|
|
|
[ch] -> handshakeServerWith sparams ctx ch
|
2012-04-27 06:28:17 +00:00
|
|
|
_ -> fail ("unexpected handshake received, excepting client hello and received " ++ show hss)
|
|
|
|
|
|
|
|
-- | Handshake for a new TLS connection
|
|
|
|
-- This is to be called at the beginning of a connection, and during renegotiation
|
|
|
|
handshake :: MonadIO m => Context -> m ()
|
|
|
|
handshake ctx = do
|
2012-07-12 08:02:10 +00:00
|
|
|
let handshakeF = case roleParams $ ctxParams ctx of
|
|
|
|
Server sparams -> handshakeServer sparams
|
|
|
|
Client cparams -> handshakeClient cparams
|
|
|
|
liftIO $ handleException $ handshakeF ctx
|
2012-04-27 06:28:17 +00:00
|
|
|
where
|
|
|
|
handleException f = catch f $ \exception -> do
|
|
|
|
let tlserror = maybe (Error_Misc $ show exception) id $ fromException exception
|
|
|
|
setEstablished ctx False
|
|
|
|
sendPacket ctx (errorToAlert tlserror)
|
|
|
|
handshakeFailed tlserror
|
|
|
|
|
2012-07-13 19:44:19 +00:00
|
|
|
|
2012-07-16 12:36:44 +00:00
|
|
|
withClientCertServer :: MonadIO m => Context -> (ServerParams -> m a) -> m a
|
2012-07-13 19:44:19 +00:00
|
|
|
withClientCertServer ctx f =
|
2012-07-16 12:36:44 +00:00
|
|
|
case roleParams $ ctxParams ctx of
|
|
|
|
Client{} ->
|
|
|
|
throwCore $ Error_Misc "not allowed in client context"
|
2012-07-14 14:49:46 +00:00
|
|
|
|
2012-07-16 12:36:44 +00:00
|
|
|
Server sp ->
|
|
|
|
f sp
|
2012-07-13 19:44:19 +00:00
|
|
|
|
2012-07-16 12:36:44 +00:00
|
|
|
withClientCertClient :: MonadIO m => Context -> (ClientParams -> m a) -> m a
|
2012-07-13 19:44:19 +00:00
|
|
|
withClientCertClient ctx f =
|
2012-07-16 12:36:44 +00:00
|
|
|
case roleParams $ ctxParams ctx of
|
|
|
|
Server{} ->
|
|
|
|
throwCore $ Error_Misc "not allowed in server context"
|
2012-07-14 14:49:46 +00:00
|
|
|
|
2012-07-16 12:36:44 +00:00
|
|
|
Client cp ->
|
|
|
|
f cp
|
2012-07-13 19:44:19 +00:00
|
|
|
|
|
|
|
throwMiscErrorOnException :: MonadIO m => String -> SomeException -> m a
|
|
|
|
throwMiscErrorOnException msg e =
|
|
|
|
throwCore $ Error_Misc $ msg ++ ": " ++ show e
|
2012-07-17 15:33:11 +00:00
|
|
|
|
|
|
|
formatHandshakeMessages :: [Bytes] -> String
|
|
|
|
formatHandshakeMessages bss =
|
|
|
|
"=====\n" ++ intercalate "\n" (map form bss) ++ "\n====="
|
|
|
|
where
|
|
|
|
form :: Bytes -> String
|
|
|
|
form bs = frm bs 0
|
|
|
|
frm bs' ofs =
|
|
|
|
let (a, b) = B.splitAt 16 bs'
|
|
|
|
in if B.null a
|
|
|
|
then []
|
|
|
|
else printf "%04x: " ofs ++ concatMap (printf "%02x") (B.unpack a) ++ "\n" ++ frm b (ofs + B.length a)
|