tls 0.7.2 → 0.8.0
raw patch · 18 files changed
+567/−326 lines, 18 filesdep ~certificate
Dependency ranges changed: certificate
Files
- Network/TLS.hs +7/−5
- Network/TLS/Cipher.hs +32/−15
- Network/TLS/Compression.hs +48/−7
- Network/TLS/Core.hs +84/−50
- Network/TLS/Crypto.hs +47/−57
- Network/TLS/MAC.hs +12/−6
- Network/TLS/Packet.hs +62/−53
- Network/TLS/Receiving.hs +42/−40
- Network/TLS/Record.hs +96/−0
- Network/TLS/Sending.hs +51/−43
- Network/TLS/State.hs +31/−23
- Network/TLS/Struct.hs +2/−6
- Network/TLS/Util.hs +21/−0
- Network/TLS/Wire.hs +1/−0
- README +0/−7
- README.md +25/−0
- TODO +1/−10
- tls.cabal +5/−4
Network/TLS.hs view
@@ -17,7 +17,7 @@ -- * Context object , TLSCtx- , ctxHandle+ , ctxConnection -- * Creating a context , client@@ -34,10 +34,12 @@ -- * Crypto Key , PrivateKey(..) -- * Compressions & Predefined compressions- , Compression+ , CompressionC(..)+ , Compression(..) , nullCompression -- * Ciphers & Predefined ciphers- , Cipher+ , Cipher(..)+ , Bulk(..) -- * Versions , Version(..) -- * Errors@@ -46,6 +48,6 @@ import Network.TLS.Struct (Version(..), TLSError(..)) import Network.TLS.Crypto (PrivateKey(..))-import Network.TLS.Cipher (Cipher(..))-import Network.TLS.Compression (Compression(..), nullCompression)+import Network.TLS.Cipher (Cipher(..), Bulk(..))+import Network.TLS.Compression (CompressionC(..), Compression(..), nullCompression) import Network.TLS.Core
Network/TLS/Cipher.hs view
@@ -1,4 +1,5 @@ {-# OPTIONS_HADDOCK hide #-}+{-# LANGUAGE ExistentialQuantification #-} -- | -- Module : Network.TLS.Cipher -- License : BSD-style@@ -7,9 +8,12 @@ -- Portability : unknown -- module Network.TLS.Cipher- ( CipherTypeFunctions(..)+ ( BulkFunctions(..) , CipherKeyExchangeType(..)+ , Bulk(..)+ , Hash(..) , Cipher(..)+ , cipherKeyBlockSize , Key , IV , cipherExchangeNeedMoreData@@ -24,13 +28,13 @@ type Key = B.ByteString type IV = B.ByteString -data CipherTypeFunctions =- CipherNoneF -- special value for 0- | CipherBlockF (Key -> IV -> B.ByteString -> B.ByteString)- (Key -> IV -> B.ByteString -> B.ByteString)- | CipherStreamF (Key -> IV)- (IV -> B.ByteString -> (B.ByteString, IV))- (IV -> B.ByteString -> (B.ByteString, IV))+data BulkFunctions =+ BulkNoneF -- special value for 0+ | BulkBlockF (Key -> IV -> B.ByteString -> B.ByteString)+ (Key -> IV -> B.ByteString -> B.ByteString)+ | BulkStreamF (Key -> IV)+ (IV -> B.ByteString -> (B.ByteString, IV))+ (IV -> B.ByteString -> (B.ByteString, IV)) data CipherKeyExchangeType = CipherKeyExchange_RSA@@ -45,20 +49,33 @@ | CipherKeyExchange_ECDHE_ECDSA deriving (Show,Eq) +data Bulk = Bulk+ { bulkName :: String+ , bulkKeySize :: Int+ , bulkIVSize :: Int+ , bulkBlockSize :: Int+ , bulkF :: BulkFunctions+ }++data Hash = Hash+ { hashName :: String+ , hashSize :: Int+ , hashF :: B.ByteString -> B.ByteString+ }+ -- | Cipher algorithm data Cipher = Cipher { cipherID :: Word16 , cipherName :: String- , cipherDigestSize :: Word8- , cipherKeySize :: Word8- , cipherIVSize :: Word8- , cipherKeyBlockSize :: Word8- , cipherPaddingSize :: Word8+ , cipherHash :: Hash+ , cipherBulk :: Bulk , cipherKeyExchange :: CipherKeyExchangeType- , cipherMACHash :: B.ByteString -> B.ByteString- , cipherF :: CipherTypeFunctions , cipherMinVer :: Maybe Version }++cipherKeyBlockSize :: Cipher -> Int+cipherKeyBlockSize cipher = 2 * (hashSize (cipherHash cipher) + bulkIVSize bulk + bulkKeySize bulk)+ where bulk = cipherBulk cipher instance Show Cipher where show c = cipherName c
Network/TLS/Compression.hs view
@@ -1,4 +1,5 @@ {-# OPTIONS_HADDOCK hide #-}+{-# LANGUAGE ExistentialQuantification #-} -- | -- Module : Network.TLS.Compression -- License : BSD-style@@ -7,22 +8,62 @@ -- Portability : unknown -- module Network.TLS.Compression- ( Compression(..)+ ( CompressionC(..)+ , Compression(..) , nullCompression++ -- * member redefined for the class abstraction+ , compressionID+ , compressionDeflate+ , compressionInflate++ -- * helper+ , compressionIntersectID ) where import Data.Word import Data.ByteString (ByteString)+import Control.Arrow (first) --- | Compression algorithm-data Compression = Compression- { compressionID :: Word8- , compressionFct :: (ByteString -> ByteString)- }+-- | supported compression algorithms need to be part of this class+class CompressionC a where+ compressionCID :: a -> Word8+ compressionCDeflate :: a -> ByteString -> (a, ByteString)+ compressionCInflate :: a -> ByteString -> (a, ByteString) +-- | every compression need to be wrapped in this, to fit in structure+data Compression = forall a . CompressionC a => Compression a++-- | return the associated ID for this algorithm+compressionID :: Compression -> Word8+compressionID (Compression c) = compressionCID c++-- | deflate (compress) a bytestring using a compression context and return the result+-- along with the new compression context.+compressionDeflate :: ByteString -> Compression -> (Compression, ByteString)+compressionDeflate bytes (Compression c) = first Compression $ compressionCDeflate c bytes++-- | inflate (decompress) a bytestring using a compression context and return the result+-- along the new compression context.+compressionInflate :: ByteString -> Compression -> (Compression, ByteString)+compressionInflate bytes (Compression c) = first Compression $ compressionCInflate c bytes+ instance Show Compression where show = show . compressionID +-- | intersect a list of ids commonly given by the other side with a list of compression+-- the function keeps the list of compression in order, to be able to find quickly the prefered+-- compression.+compressionIntersectID :: [Compression] -> [Word8] -> [Compression]+compressionIntersectID l ids = filter (\c -> elem (compressionID c) ids) l++data NullCompression = NullCompression++instance CompressionC NullCompression where+ compressionCID _ = 0+ compressionCDeflate s b = (s, b)+ compressionCInflate s b = (s, b)+ -- | default null compression nullCompression :: Compression-nullCompression = Compression { compressionID = 0, compressionFct = id }+nullCompression = Compression NullCompression
Network/TLS/Core.hs view
@@ -18,7 +18,7 @@ -- * Context object , TLSCtx- , ctxHandle+ , ctxConnection , ctxEOF -- * Internal packet sending and receiving@@ -27,7 +27,9 @@ -- * Creating a context , client+ , clientWith , server+ , serverWith -- * Initialisation and Termination of context , bye@@ -39,6 +41,7 @@ ) where import Network.TLS.Struct+import Network.TLS.Record import Network.TLS.Cipher import Network.TLS.Compression import Network.TLS.Crypto@@ -93,7 +96,7 @@ , pUseSecureRenegotiation :: Bool -- notify that we want to use secure renegotation , pCertificates :: [(X509, Maybe PrivateKey)] -- ^ the cert chain for this context with the associated keys if any. , pLogging :: TLSLogging -- ^ callback for logging- , onCertificatesRecv :: ([X509] -> IO TLSCertificateUsage) -- ^ callback to verify received cert chain.+ , onCertificatesRecv :: [X509] -> IO TLSCertificateUsage -- ^ callback to verify received cert chain. } defaultLogging :: TLSLogging@@ -107,7 +110,7 @@ defaultParams :: TLSParams defaultParams = TLSParams { pConnectVersion = TLS10- , pAllowedVersions = [TLS10,TLS11]+ , pAllowedVersions = [TLS10,TLS11,TLS12] , pCiphers = [] , pCompressions = [nullCompression] , pWantClientCert = False@@ -128,35 +131,54 @@ ]) ++ " }" -- | A TLS Context is a handle augmented by tls specific state and parameters-data TLSCtx = TLSCtx- { ctxHandle :: Handle -- ^ return the handle associated with this context- , ctxParams :: TLSParams- , ctxState :: MVar TLSState- , ctxEOF_ :: IORef Bool -- ^ is the handle as EOFed or not.+data TLSCtx a = TLSCtx+ { ctxConnection :: a -- ^ return the connection object associated with this context+ , ctxParams :: TLSParams+ , ctxState :: MVar TLSState+ , ctxEOF_ :: IORef Bool -- ^ is the handle has EOFed or not.+ , ctxConnectionFlush :: IO ()+ , ctxConnectionSend :: Bytes -> IO ()+ , ctxConnectionRecv :: Int -> IO Bytes } -ctxEOF :: MonadIO m => TLSCtx -> m Bool+connectionFlush :: TLSCtx c -> IO ()+connectionFlush c = (ctxConnectionFlush c)++connectionSend :: TLSCtx c -> Bytes -> IO ()+connectionSend c b = (ctxConnectionSend c) b++connectionRecv :: TLSCtx c -> Int -> IO Bytes+connectionRecv c sz = (ctxConnectionRecv c) sz++ctxEOF :: MonadIO m => TLSCtx a -> m Bool ctxEOF ctx = liftIO (readIORef $ ctxEOF_ ctx) throwCore :: (MonadIO m, Exception e) => e -> m a throwCore = liftIO . throwIO -newCtx :: Handle -> TLSParams -> TLSState -> IO TLSCtx-newCtx handle params st = do- hSetBuffering handle NoBuffering+newCtxWith :: c -> IO () -> (Bytes -> IO ()) -> (Int -> IO Bytes) -> TLSParams -> TLSState -> IO (TLSCtx c)+newCtxWith c flushF sendF recvF params st = do stvar <- newMVar st eof <- newIORef False return $ TLSCtx- { ctxHandle = handle- , ctxParams = params- , ctxState = stvar- , ctxEOF_ = eof+ { ctxConnection = c+ , ctxParams = params+ , ctxState = stvar+ , ctxEOF_ = eof+ , ctxConnectionFlush = flushF+ , ctxConnectionSend = sendF+ , ctxConnectionRecv = recvF } -ctxLogging :: TLSCtx -> TLSLogging+newCtx :: Handle -> TLSParams -> TLSState -> IO (TLSCtx Handle)+newCtx handle params st = do+ hSetBuffering handle NoBuffering+ newCtxWith handle (hFlush handle) (B.hPut handle) (B.hGet handle) params st++ctxLogging :: TLSCtx a -> TLSLogging ctxLogging = pLogging . ctxParams -usingState :: MonadIO m => TLSCtx -> TLSSt a -> m (Either TLSError a)+usingState :: MonadIO m => TLSCtx c -> TLSSt a -> m (Either TLSError a) usingState ctx f = liftIO (takeMVar mvar) >>= \st -> liftIO $ onException (execAndStore st) (putMVar mvar st) where mvar = ctxState ctx@@ -165,17 +187,17 @@ putMVar mvar newst return a -usingState_ :: MonadIO m => TLSCtx -> TLSSt a -> m a+usingState_ :: MonadIO m => TLSCtx c -> TLSSt a -> m a usingState_ ctx f = do ret <- usingState ctx f case ret of Left err -> throwCore err Right r -> return r -getStateRNG :: MonadIO m => TLSCtx -> Int -> m Bytes+getStateRNG :: MonadIO m => TLSCtx c -> Int -> m Bytes getStateRNG ctx n = usingState_ ctx (genTLSRandom n) -whileStatus :: MonadIO m => TLSCtx -> (TLSStatus -> Bool) -> m a -> m ()+whileStatus :: MonadIO m => TLSCtx c -> (TLSStatus -> Bool) -> m a -> m () whileStatus ctx p a = do b <- usingState_ ctx (p . stStatus <$> get) when b (a >> whileStatus ctx p a)@@ -184,12 +206,12 @@ errorToAlert (Error_Protocol (_, _, ad)) = Alert [(AlertLevel_Fatal, ad)] errorToAlert _ = Alert [(AlertLevel_Fatal, InternalError)] -setEOF :: MonadIO m => TLSCtx -> m ()+setEOF :: MonadIO m => TLSCtx c -> m () setEOF ctx = liftIO $ writeIORef (ctxEOF_ ctx) True -readExact :: MonadIO m => TLSCtx -> Int -> m Bytes+readExact :: MonadIO m => TLSCtx c -> Int -> m Bytes readExact ctx sz = do- hdrbs <- liftIO $ B.hGet (ctxHandle ctx) sz+ hdrbs <- liftIO $ connectionRecv ctx sz when (B.length hdrbs < sz) $ do setEOF ctx if B.null hdrbs@@ -200,7 +222,7 @@ -- | receive one packet from the context that contains 1 or -- many messages (many only in case of handshake). if will returns a -- TLSError if the packet is unexpected or malformed-recvPacket :: MonadIO m => TLSCtx -> m (Either TLSError Packet)+recvPacket :: MonadIO m => TLSCtx c -> m (Either TLSError Packet) recvPacket ctx = do hdrbs <- readExact ctx 5 case decodeHeader hdrbs of@@ -212,13 +234,13 @@ where recvLength header readlen = do content <- readExact ctx (fromIntegral readlen) liftIO $ (loggingIORecv $ ctxLogging ctx) header content- pkt <- usingState ctx $ readPacket header (EncryptedData content)+ pkt <- usingState ctx $ readPacket $ rawToRecord header (fragmentCiphertext content) case pkt of Right p -> liftIO $ (loggingPacketRecv $ ctxLogging ctx) $ show p _ -> return () return pkt -recvPacketSuccess :: MonadIO m => TLSCtx -> m ()+recvPacketSuccess :: MonadIO m => TLSCtx c -> m () recvPacketSuccess ctx = do pkt <- recvPacket ctx case pkt of@@ -226,22 +248,34 @@ Right _ -> return () -- | Send one packet to the context-sendPacket :: MonadIO m => TLSCtx -> Packet -> m ()+sendPacket :: MonadIO m => TLSCtx c -> Packet -> m () sendPacket ctx pkt = do liftIO $ (loggingPacketSent $ ctxLogging ctx) (show pkt) dataToSend <- usingState_ ctx $ writePacket pkt liftIO $ (loggingIOSent $ ctxLogging ctx) dataToSend- liftIO $ B.hPut (ctxHandle ctx) dataToSend+ liftIO $ connectionSend ctx dataToSend +-- | Create a new Client context with a configuration, a RNG, a generic connection and the connection operation.+clientWith :: (MonadIO m, CryptoRandomGen g) => TLSParams -> g -> c -> IO () -> (Bytes -> IO ()) -> (Int -> IO Bytes) -> m (TLSCtx c)+clientWith params rng connection flushF sendF recvF =+ liftIO $ newCtxWith connection flushF sendF recvF params st+ where st = (newTLSState rng) { stClientContext = True }+ -- | Create a new Client context with a configuration, a RNG, and a Handle. -- It reconfigures the handle buffermode to noBuffering-client :: (MonadIO m, CryptoRandomGen g) => TLSParams -> g -> Handle -> m TLSCtx+client :: (MonadIO m, CryptoRandomGen g) => TLSParams -> g -> Handle -> m (TLSCtx Handle) client params rng handle = liftIO $ newCtx handle params st where st = (newTLSState rng) { stClientContext = True } +-- | Create a new Server context with a configuration, a RNG, a generic connection and the connection operation.+serverWith :: (MonadIO m, CryptoRandomGen g) => TLSParams -> g -> c -> IO () -> (Bytes -> IO ()) -> (Int -> IO Bytes) -> m (TLSCtx c)+serverWith params rng connection flushF sendF recvF =+ liftIO $ newCtxWith connection flushF sendF recvF params st+ where st = (newTLSState rng) { stClientContext = False }+ -- | Create a new Server context with a configuration, a RNG, and a Handle. -- It reconfigures the handle buffermode to noBuffering-server :: (MonadIO m, CryptoRandomGen g) => TLSParams -> g -> Handle -> m TLSCtx+server :: (MonadIO m, CryptoRandomGen g) => TLSParams -> g -> Handle -> m (TLSCtx Handle) server params rng handle = liftIO $ newCtx handle params st where st = (newTLSState rng) { stClientContext = False } @@ -250,12 +284,12 @@ -- the session might not be resumable (for version < TLS1.2). -- -- this doesn't actually close the handle-bye :: MonadIO m => TLSCtx -> m ()+bye :: MonadIO m => TLSCtx c -> m () bye ctx = sendPacket ctx $ Alert [(AlertLevel_Warning, CloseNotify)] -- client part of handshake. send a bunch of handshake of client -- values intertwined with response from the server.-handshakeClient :: MonadIO m => TLSCtx -> m ()+handshakeClient :: MonadIO m => TLSCtx c -> m () handshakeClient ctx = do -- Send ClientHello crand <- getStateRNG ctx 32 >>= return . ClientRandom@@ -283,7 +317,7 @@ {- FIXME not implemented yet -} sendPacket ctx ChangeCipherSpec- liftIO $ hFlush $ ctxHandle ctx+ liftIO $ connectionFlush ctx -- Send Finished cf <- usingState_ ctx $ getHandshakeDigest True@@ -342,7 +376,7 @@ certificateRejected (CertificateRejectOther s) = throwCore $ Error_Protocol ("certificate rejected: " ++ s, True, CertificateUnknown) -handshakeServerWith :: MonadIO m => TLSCtx -> Handshake -> m ()+handshakeServerWith :: MonadIO m => TLSCtx c -> Handshake -> m () handshakeServerWith ctx (ClientHello ver _ _ ciphers compressions _) = do -- Handle Client hello when (ver == SSL2) $ throwCore $ Error_Protocol ("ssl2 is not supported", True, ProtocolVersion)@@ -350,17 +384,17 @@ 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 (commonCompressions == []) $+ 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 = Just usedCompression+ { stVersion = ver+ , stCipher = Just usedCipher+ , stCompression = usedCompression }) -- send Server Data until ServerHelloDone handshakeSendServerData- liftIO $ hFlush $ ctxHandle ctx+ liftIO $ connectionFlush ctx -- Receive client info until client Finished. whileStatus ctx (/= (StatusHandshake HsStatusClientFinished)) (recvPacketSuccess ctx)@@ -371,14 +405,14 @@ cf <- usingState_ ctx $ getHandshakeDigest False sendPacket ctx (Handshake [Finished cf]) - liftIO $ hFlush $ ctxHandle ctx+ liftIO $ connectionFlush ctx return () where params = ctxParams ctx commonCiphers = intersect ciphers (map cipherID $ pCiphers params) usedCipher = fromJust $ find (\c -> cipherID c == head commonCiphers) (pCiphers params)- commonCompressions = intersect compressions (map compressionID $ pCompressions params)- usedCompression = fromJust $ find (\c -> compressionID c == head commonCompressions) (pCompressions params)+ commonCompressions = compressionIntersectID (pCompressions params) compressions+ usedCompression = head commonCompressions srvCerts = map fst $ pCertificates params privKeys = map snd $ pCertificates params needKeyXchg = cipherExchangeNeedMoreData $ cipherKeyExchange usedCipher@@ -423,7 +457,7 @@ handshakeServerWith _ _ = fail "unexpected handshake type received. expecting client hello" -- after receiving a client hello, we need to redo a handshake -}-handshakeServer :: MonadIO m => TLSCtx -> m ()+handshakeServer :: MonadIO m => TLSCtx c -> m () handshakeServer ctx = do pkts <- recvPacket ctx case pkts of@@ -432,7 +466,7 @@ -- | Handshake for a new TLS connection -- This is to be called at the beginning of a connection, and during renegociation-handshake :: MonadIO m => TLSCtx -> m Bool+handshake :: MonadIO m => TLSCtx c -> m Bool handshake ctx = do cc <- usingState_ ctx (stClientContext <$> get) liftIO $ handleException $ if cc then handshakeClient ctx else handshakeServer ctx@@ -444,10 +478,10 @@ -- | sendData sends a bunch of data. -- It will automatically chunk data to acceptable packet size-sendData :: MonadIO m => TLSCtx -> L.ByteString -> m ()+sendData :: MonadIO m => TLSCtx c -> L.ByteString -> m () sendData ctx dataToSend = do eofed <- ctxEOF ctx- when eofed $ liftIO $ throwIO $ mkIOError eofErrorType "sendData" (Just (ctxHandle ctx)) Nothing+ when eofed $ liftIO $ throwIO $ mkIOError eofErrorType "sendData" Nothing Nothing mapM_ sendDataChunk (L.toChunks dataToSend) where sendDataChunk d = if B.length d > 16384 then do@@ -459,10 +493,10 @@ -- | recvData get data out of Data packet, and automatically renegociate if -- a Handshake ClientHello is received-recvData :: MonadIO m => TLSCtx -> m L.ByteString+recvData :: MonadIO m => TLSCtx c -> m L.ByteString recvData ctx = do eofed <- ctxEOF ctx- when eofed $ liftIO $ throwIO $ mkIOError eofErrorType "recvData" (Just (ctxHandle ctx)) Nothing+ when eofed $ liftIO $ throwIO $ mkIOError eofErrorType "recvData" Nothing Nothing pkt <- recvPacket ctx case pkt of -- on server context receiving a client hello == renegociation@@ -478,5 +512,5 @@ setEOF ctx return L.empty Right (AppData x) -> return $ L.fromChunks [x]- Right p -> error ("error unexpected packet: p" ++ show p)+ Right p -> error ("error unexpected packet: " ++ show p) Left err -> error ("error received: " ++ show err)
Network/TLS/Crypto.hs view
@@ -1,23 +1,15 @@ {-# OPTIONS_HADDOCK hide #-}+{-# LANGUAGE ExistentialQuantification #-} module Network.TLS.Crypto- ( HashType(..)- , HashCtx-- -- * incremental interface with algorithm type wrapping for genericity- , initHash- , updateHash- , finalizeHash+ ( HashCtx(..)+ , hashInit+ , hashUpdate+ , hashUpdateSSL+ , hashFinal - -- * single pass lazy bytestring interface for each algorithm- , hashMD5- , hashSHA1- -- * incremental interface for each algorithm- , initMD5- , updateMD5- , finalizeMD5- , initSHA1- , updateSHA1- , finalizeSHA1+ -- * constructor+ , hashMD5SHA1+ , hashSHA256 -- * key exchange generic interface , PublicKey(..)@@ -27,6 +19,7 @@ , KxError(..) ) where +import qualified Crypto.Hash.SHA256 as SHA256 import qualified Crypto.Hash.SHA1 as SHA1 import qualified Crypto.Hash.MD5 as MD5 import qualified Data.ByteString as B@@ -47,61 +40,58 @@ data KxError = RSAError RSA.Error deriving (Show) -data HashCtx =- SHA1 !SHA1.Ctx- | MD5 !MD5.Ctx- data KeyXchg = KxRSA RSA.PublicKey RSA.PrivateKey deriving (Show) -instance Show HashCtx where- show (SHA1 _) = "sha1"- show (MD5 _) = "md5"--data HashType = HashTypeSHA1 | HashTypeMD5--{- MD5 -}--initMD5 :: MD5.Ctx-initMD5 = MD5.init--updateMD5 :: MD5.Ctx -> ByteString -> MD5.Ctx-updateMD5 = MD5.update+class HashCtxC a where+ hashCName :: a -> String+ hashCInit :: a -> a+ hashCUpdate :: a -> B.ByteString -> a+ hashCUpdateSSL :: a -> (B.ByteString,B.ByteString) -> a+ hashCFinal :: a -> B.ByteString -finalizeMD5 :: MD5.Ctx -> ByteString-finalizeMD5 = MD5.finalize+data HashCtx = forall h . HashCtxC h => HashCtx h -hashMD5 :: ByteString -> ByteString-hashMD5 = MD5.hash+instance Show HashCtx where+ show (HashCtx c) = hashCName c -{- SHA1 -}+{- MD5 & SHA1 joined -}+data HashMD5SHA1 = HashMD5SHA1 SHA1.Ctx MD5.Ctx -initSHA1 :: SHA1.Ctx-initSHA1 = SHA1.init+instance HashCtxC HashMD5SHA1 where+ hashCName _ = "MD5-SHA1"+ hashCInit _ = HashMD5SHA1 SHA1.init MD5.init+ hashCUpdate (HashMD5SHA1 sha1ctx md5ctx) b = HashMD5SHA1 (SHA1.update sha1ctx b) (MD5.update md5ctx b)+ hashCUpdateSSL (HashMD5SHA1 sha1ctx md5ctx) (b1,b2) = HashMD5SHA1 (SHA1.update sha1ctx b2) (MD5.update md5ctx b1)+ hashCFinal (HashMD5SHA1 sha1ctx md5ctx) = B.concat [MD5.finalize md5ctx, SHA1.finalize sha1ctx] -updateSHA1 :: SHA1.Ctx -> ByteString -> SHA1.Ctx-updateSHA1 = SHA1.update+data HashSHA256 = HashSHA256 SHA256.Ctx -finalizeSHA1 :: SHA1.Ctx -> ByteString-finalizeSHA1 = SHA1.finalize+instance HashCtxC HashSHA256 where+ hashCName _ = "SHA256"+ hashCInit _ = HashSHA256 SHA256.init+ hashCUpdate (HashSHA256 ctx) b = HashSHA256 (SHA256.update ctx b)+ hashCUpdateSSL _ _ = undefined+ hashCFinal (HashSHA256 ctx) = SHA256.finalize ctx -hashSHA1 :: ByteString -> ByteString-hashSHA1 = SHA1.hash+-- functions to use the hidden class.+hashInit :: HashCtx -> HashCtx+hashInit (HashCtx h) = HashCtx $ hashCInit h -{- generic Hashing -}+hashUpdate :: HashCtx -> B.ByteString -> HashCtx+hashUpdate (HashCtx h) b = HashCtx $ hashCUpdate h b -initHash :: HashType -> HashCtx-initHash HashTypeSHA1 = SHA1 (initSHA1)-initHash HashTypeMD5 = MD5 (initMD5)+hashUpdateSSL :: HashCtx -> (B.ByteString,B.ByteString) -> HashCtx+hashUpdateSSL (HashCtx h) bs = HashCtx $ hashCUpdateSSL h bs -updateHash :: HashCtx -> B.ByteString -> HashCtx-updateHash (SHA1 ctx) = SHA1 . updateSHA1 ctx-updateHash (MD5 ctx) = MD5 . updateMD5 ctx+hashFinal :: HashCtx -> B.ByteString+hashFinal (HashCtx h) = hashCFinal h -finalizeHash :: HashCtx -> B.ByteString-finalizeHash (SHA1 ctx) = finalizeSHA1 ctx-finalizeHash (MD5 ctx) = finalizeMD5 ctx+-- real hash constructors+hashMD5SHA1, hashSHA256 :: HashCtx+hashMD5SHA1 = HashCtx (HashMD5SHA1 SHA1.init MD5.init)+hashSHA256 = HashCtx (HashSHA256 SHA256.init) {- key exchange methods encrypt and decrypt for each supported algorithm -} generalizeRSAError :: Either RSA.Error a -> Either KxError a
Network/TLS/MAC.hs view
@@ -6,6 +6,7 @@ , hmac , prf_MD5 , prf_SHA1+ , prf_SHA256 , prf_MD5SHA1 ) where @@ -16,14 +17,16 @@ import Data.ByteString (ByteString) import Data.Bits (xor) -macSSL :: (ByteString -> ByteString) -> ByteString -> ByteString -> ByteString+type HMAC = ByteString -> ByteString -> ByteString++macSSL :: (ByteString -> ByteString) -> HMAC macSSL f secret msg = f $! B.concat [ secret, B.replicate padlen 0x5c, f $! B.concat [ secret, B.replicate padlen 0x36, msg ] ] where -- get the type of algorithm out of the digest length by using the hash fct. padlen = if (B.length $ f B.empty) == 16 then 48 else 40 -hmac :: (ByteString -> ByteString) -> Int -> ByteString -> ByteString -> ByteString+hmac :: (ByteString -> ByteString) -> Int -> HMAC hmac f bl secret msg = f $! B.append opad (f $! B.append ipad msg) where@@ -35,16 +38,16 @@ kt = if B.length secret > fromIntegral bl then f secret else secret pad = B.replicate (fromIntegral bl - B.length kt) 0 -hmacMD5 :: ByteString -> ByteString -> ByteString+hmacMD5 :: HMAC hmacMD5 secret msg = hmac MD5.hash 64 secret msg -hmacSHA1 :: ByteString -> ByteString -> ByteString+hmacSHA1 :: HMAC hmacSHA1 secret msg = hmac SHA1.hash 64 secret msg -hmacSHA256 :: ByteString -> ByteString -> ByteString+hmacSHA256 :: HMAC hmacSHA256 secret msg = hmac SHA256.hash 64 secret msg -hmacIter :: (ByteString -> ByteString -> ByteString) -> ByteString -> ByteString -> ByteString -> Int -> [ByteString]+hmacIter :: HMAC -> ByteString -> ByteString -> ByteString -> Int -> [ByteString] hmacIter f secret seed aprev len = let an = f secret aprev in let out = f secret (B.concat [an, seed]) in@@ -66,3 +69,6 @@ slen = B.length secret s1 = B.take (slen `div` 2 + slen `mod` 2) secret s2 = B.drop (slen `div` 2) secret++prf_SHA256 :: ByteString -> ByteString -> Int -> ByteString+prf_SHA256 secret seed len = B.concat $ hmacIter hmacSHA256 secret seed seed len
Network/TLS/Packet.hs view
@@ -63,6 +63,9 @@ import qualified Data.ByteString.Char8 as BC import qualified Data.ByteString.Lazy as L +import qualified Crypto.Hash.SHA1 as SHA1+import qualified Crypto.Hash.MD5 as MD5+ data CurrentParams = CurrentParams { cParamsVersion :: Version -- ^ current protocol version , cParamsKeyXchgType :: CipherKeyExchangeType -- ^ current key exchange type@@ -105,11 +108,7 @@ - decode and encode headers -} decodeHeader :: ByteString -> Either TLSError Header-decodeHeader = runGetErr "header" $ do- ty <- getHeaderType- v <- getVersion- len <- getWord16- return $ Header ty v len+decodeHeader = runGetErr "header" $ liftM3 Header getHeaderType getVersion getWord16 encodeHeader :: Header -> ByteString encodeHeader (Header pt ver len) = runPut (putHeaderType pt >> putVersion ver >> putWord16 len)@@ -141,7 +140,7 @@ encodeAlerts :: [(AlertLevel, AlertDescription)] -> ByteString encodeAlerts l = runPut $ mapM_ encodeAlert l- where encodeAlert (al, ad) = (putWord8 (valOfType al) >> putWord8 (valOfType ad))+ where encodeAlert (al, ad) = putWord8 (valOfType al) >> putWord8 (valOfType ad) {- decode and encode HANDSHAKE -} decodeHandshakeHeader :: Get (HandshakeType, Bytes)@@ -207,8 +206,7 @@ decodeCertificates :: Get Handshake decodeCertificates = do- certslen <- getWord24- certs <- getCerts certslen >>= return . map (decodeCertificate . L.fromChunks . (:[]))+ certs <- getWord24 >>= getCerts >>= return . map (decodeCertificate . L.fromChunks . (:[])) let (l, r) = partitionEithers certs if length l > 0 then fail ("error certificate parsing: " ++ show l)@@ -219,14 +217,16 @@ opaque <- remaining >>= getBytes return $ Finished $ opaque -getSignatureHashAlgorithm :: Int -> Get [ (HashAlgorithm, SignatureAlgorithm) ]-getSignatureHashAlgorithm 0 = return []-getSignatureHashAlgorithm len = do+getSignatureHashAlgorithm :: Get (HashAlgorithm, SignatureAlgorithm)+getSignatureHashAlgorithm = do h <- fromJust . valToType <$> getWord8 s <- fromJust . valToType <$> getWord8- xs <- getSignatureHashAlgorithm (len - 2)- return ((h, s) : xs)+ return (h,s) +getSignatureHashAlgorithms :: Int -> Get [ (HashAlgorithm, SignatureAlgorithm) ]+getSignatureHashAlgorithms 0 = return []+getSignatureHashAlgorithms len = liftM2 (:) getSignatureHashAlgorithm (getSignatureHashAlgorithms (len-2))+ decodeCertRequest :: CurrentParams -> Get Handshake decodeCertRequest cp = do certTypes <- map (fromJust . valToType . fromIntegral) <$> getWords8@@ -234,7 +234,7 @@ sigHashAlgs <- if cParamsVersion cp >= TLS12 then do sighashlen <- getWord16- Just <$> getSignatureHashAlgorithm (fromIntegral sighashlen)+ Just <$> getSignatureHashAlgorithms (fromIntegral sighashlen) else return Nothing dNameLen <- getWord16 when (cParamsVersion cp < TLS12 && dNameLen < 3) $ fail "certrequest distinguishname not of the correct size"@@ -372,10 +372,8 @@ len -> Session . Just <$> getBytes len putSession :: Session -> Put-putSession (Session session) =- case session of- Nothing -> putWord8 0- Just s -> putWord8 (fromIntegral $ B.length s) >> putBytes s+putSession (Session Nothing) = putWord8 0+putSession (Session (Just s)) = putWord8 (fromIntegral $ B.length s) >> putBytes s getCerts :: Int -> Get [Bytes] getCerts 0 = return []@@ -446,61 +444,72 @@ {- - generate things for packet content -}-generateMasterSecret_TLS, generateMasterSecret_SSL :: Bytes -> ClientRandom -> ServerRandom -> Bytes-generateMasterSecret_TLS premasterSecret (ClientRandom c) (ServerRandom s) =- prf_MD5SHA1 premasterSecret seed 48- where- seed = B.concat [ "master secret", c, s ]+type PRF = Bytes -> Bytes -> Int -> Bytes +generateMasterSecret_SSL :: Bytes -> ClientRandom -> ServerRandom -> Bytes generateMasterSecret_SSL premasterSecret (ClientRandom c) (ServerRandom s) = B.concat $ map (computeMD5) ["A","BB","CCC"] where- computeMD5 label = hashMD5 $ B.concat [ premasterSecret, computeSHA1 label ]- computeSHA1 label = hashSHA1 $ B.concat [ label, premasterSecret, c, s ]+ computeMD5 label = MD5.hash $ B.concat [ premasterSecret, computeSHA1 label ]+ computeSHA1 label = SHA1.hash $ B.concat [ label, premasterSecret, c, s ] +generateMasterSecret_TLS :: PRF -> Bytes -> ClientRandom -> ServerRandom -> Bytes+generateMasterSecret_TLS prf premasterSecret (ClientRandom c) (ServerRandom s) =+ prf premasterSecret seed 48+ where+ seed = B.concat [ "master secret", c, s ]+ generateMasterSecret :: Version -> Bytes -> ClientRandom -> ServerRandom -> Bytes-generateMasterSecret ver =- if ver < TLS10 then generateMasterSecret_SSL else generateMasterSecret_TLS+generateMasterSecret SSL2 = generateMasterSecret_SSL+generateMasterSecret SSL3 = generateMasterSecret_SSL+generateMasterSecret TLS10 = generateMasterSecret_TLS prf_MD5SHA1+generateMasterSecret TLS11 = generateMasterSecret_TLS prf_MD5SHA1+generateMasterSecret TLS12 = generateMasterSecret_TLS prf_SHA256 -generateKeyBlock_TLS :: ClientRandom -> ServerRandom -> Bytes -> Int -> Bytes-generateKeyBlock_TLS (ClientRandom c) (ServerRandom s) mastersecret kbsize =- prf_MD5SHA1 mastersecret seed kbsize- where- seed = B.concat [ "key expansion", s, c ]+generateKeyBlock_TLS :: PRF -> ClientRandom -> ServerRandom -> Bytes -> Int -> Bytes+generateKeyBlock_TLS prf (ClientRandom c) (ServerRandom s) mastersecret kbsize =+ prf mastersecret seed kbsize where seed = B.concat [ "key expansion", s, c ] generateKeyBlock_SSL :: ClientRandom -> ServerRandom -> Bytes -> Int -> Bytes generateKeyBlock_SSL (ClientRandom c) (ServerRandom s) mastersecret kbsize = B.concat $ map computeMD5 $ take ((kbsize `div` 16) + 1) labels where labels = [ uncurry BC.replicate x | x <- zip [1..] ['A'..'Z'] ]- computeMD5 label = hashMD5 $ B.concat [ mastersecret, computeSHA1 label ]- computeSHA1 label = hashSHA1 $ B.concat [ label, mastersecret, s, c ]+ computeMD5 label = MD5.hash $ B.concat [ mastersecret, computeSHA1 label ]+ computeSHA1 label = SHA1.hash $ B.concat [ label, mastersecret, s, c ] generateKeyBlock :: Version -> ClientRandom -> ServerRandom -> Bytes -> Int -> Bytes-generateKeyBlock ver =- if ver < TLS10 then generateKeyBlock_SSL else generateKeyBlock_TLS+generateKeyBlock SSL2 = generateKeyBlock_SSL+generateKeyBlock SSL3 = generateKeyBlock_SSL+generateKeyBlock TLS10 = generateKeyBlock_TLS prf_MD5SHA1+generateKeyBlock TLS11 = generateKeyBlock_TLS prf_MD5SHA1+generateKeyBlock TLS12 = generateKeyBlock_TLS prf_SHA256 -generateFinished_TLS :: Bytes -> Bytes -> HashCtx -> HashCtx -> Bytes-generateFinished_TLS label mastersecret md5ctx sha1ctx =- prf_MD5SHA1 mastersecret seed 12+generateFinished_TLS :: PRF -> Bytes -> Bytes -> HashCtx -> Bytes+generateFinished_TLS prf label mastersecret hashctx = prf mastersecret seed 12 where- seed = B.concat [ label, finalizeHash md5ctx, finalizeHash sha1ctx ]+ seed = B.concat [ label, hashFinal hashctx ] -generateFinished_SSL :: Bytes -> Bytes -> HashCtx -> HashCtx -> Bytes-generateFinished_SSL sender mastersecret md5ctx sha1ctx =- B.concat [md5hash, sha1hash]+generateFinished_SSL :: Bytes -> Bytes -> HashCtx -> Bytes+generateFinished_SSL sender mastersecret hashctx = B.concat [md5hash, sha1hash] where- md5hash = hashMD5 $ B.concat [ mastersecret, pad2, md5left ]- sha1hash = hashSHA1 $ B.concat [ mastersecret, B.take 40 pad2, sha1left ]- md5left = finalizeHash $ foldl updateHash md5ctx [ sender, mastersecret, pad1 ]- sha1left = finalizeHash $ foldl updateHash sha1ctx [ sender, mastersecret, B.take 40 pad1 ]+ md5hash = MD5.hash $ B.concat [ mastersecret, pad2, md5left ]+ sha1hash = SHA1.hash $ B.concat [ mastersecret, B.take 40 pad2, sha1left ]++ lefthash = hashFinal $ flip hashUpdateSSL (pad1, B.take 40 pad1)+ $ foldl hashUpdate hashctx [sender,mastersecret]+ (md5left,sha1left) = B.splitAt 16 lefthash pad2 = B.replicate 48 0x5c pad1 = B.replicate 48 0x36 -generateClientFinished :: Version -> Bytes -> HashCtx -> HashCtx -> Bytes-generateClientFinished ver =- if ver < TLS10 then generateFinished_SSL "CLNT" else generateFinished_TLS "client finished"+generateClientFinished :: Version -> Bytes -> HashCtx -> Bytes+generateClientFinished ver+ | ver < TLS10 = generateFinished_SSL "CLNT"+ | ver < TLS12 = generateFinished_TLS prf_MD5SHA1 "client finished"+ | otherwise = generateFinished_TLS prf_SHA256 "client finished" -generateServerFinished :: Version -> Bytes -> HashCtx -> HashCtx -> Bytes-generateServerFinished ver =- if ver < TLS10 then generateFinished_SSL "SRVR" else generateFinished_TLS "server finished"+generateServerFinished :: Version -> Bytes -> HashCtx -> Bytes+generateServerFinished ver+ | ver < TLS10 = generateFinished_SSL "SRVR"+ | ver < TLS12 = generateFinished_TLS prf_MD5SHA1 "server finished"+ | otherwise = generateFinished_TLS prf_SHA256 "server finished"
Network/TLS/Receiving.hs view
@@ -23,9 +23,11 @@ import Network.TLS.Util import Network.TLS.Cap import Network.TLS.Struct+import Network.TLS.Record import Network.TLS.Packet import Network.TLS.State import Network.TLS.Cipher+import Network.TLS.Compression import Network.TLS.Crypto import Data.Certificate.X509 @@ -35,11 +37,11 @@ returnEither (Left err) = throwError err returnEither (Right a) = return a -readPacket :: Header -> EncryptedData -> TLSSt Packet-readPacket hdr content = checkState hdr >> decryptContent hdr content >>= processPacket hdr+readPacket :: Record Ciphertext -> TLSSt Packet+readPacket record = checkState record >> decryptContent record >>= uncompressContent >>= processPacket -checkState :: Header -> TLSSt ()-checkState (Header pt _ _) =+checkState :: Record a -> TLSSt ()+checkState (Record pt _ _) = stStatus <$> get >>= \status -> unless (allowed pt status) $ throwError (err status) where err st = Error_Protocol ("unexpected message received: status=" ++ show st, True, UnexpectedMessage)@@ -54,23 +56,23 @@ allowed ProtocolType_ChangeCipherSpec (StatusHandshake HsStatusClientCertificateVerify) = True allowed _ _ = False -processPacket :: Header -> Bytes -> TLSSt Packet+processPacket :: Record Plaintext -> TLSSt Packet -processPacket (Header ProtocolType_AppData _ _) content = return $ AppData content+processPacket (Record ProtocolType_AppData _ fragment) = return $ AppData $ fragmentGetBytes fragment -processPacket (Header ProtocolType_Alert _ _) content = return . Alert =<< returnEither (decodeAlerts content)+processPacket (Record ProtocolType_Alert _ fragment) = return . Alert =<< returnEither (decodeAlerts $ fragmentGetBytes fragment) -processPacket (Header ProtocolType_ChangeCipherSpec _ _) content = do+processPacket (Record ProtocolType_ChangeCipherSpec _ fragment) = do e <- updateStatusCC False when (isJust e) $ throwError (fromJust "" e) - returnEither $ decodeChangeCipherSpec content+ returnEither $ decodeChangeCipherSpec $ fragmentGetBytes fragment switchRxEncryption isClientContext >>= \cc -> when (not cc) setKeyBlock return ChangeCipherSpec -processPacket (Header ProtocolType_Handshake ver _) dcontent = do- handshakes <- returnEither (decodeHandshakes dcontent)+processPacket (Record ProtocolType_Handshake ver fragment) = do+ handshakes <- returnEither (decodeHandshakes $ fragmentGetBytes fragment) hss <- forM handshakes $ \(ty, content) -> do hs <- processHandshake ver ty content when (finishHandshakeTypeMaterial ty) $ updateHandshakeDigestSplitted ty content@@ -155,57 +157,57 @@ cc <- stClientContext <$> get expected <- getHandshakeDigest (not cc) when (expected /= fdata) $ do- throwError $ Error_Protocol( "bad record mac", True, BadRecordMac)+ throwError $ Error_Protocol("bad record mac", True, BadRecordMac) updateVerifiedData False fdata return () -decryptContent :: Header -> EncryptedData -> TLSSt ByteString-decryptContent hdr e@(EncryptedData b) = do+-- just `decompress' by returning the data for now till we have compression implemented+uncompressContent :: Record Compressed -> TLSSt (Record Plaintext)+uncompressContent record = onRecordFragment record $ fragmentUncompress $ \bytes ->+ withCompression $ compressionInflate bytes++decryptContent :: Record Ciphertext -> TLSSt (Record Compressed)+decryptContent record = onRecordFragment record $ fragmentUncipher $ \e -> do st <- get if stRxEncrypted st- then decryptData e >>= getCipherData hdr- else return b+ then decryptData e >>= getCipherData record+ else return e -getCipherData :: Header -> CipherData -> TLSSt ByteString-getCipherData hdr cdata = do+getCipherData :: Record a -> CipherData -> TLSSt ByteString+getCipherData (Record pt ver _) cdata = do -- check if the MAC is valid. macValid <- case cipherDataMAC cdata of Nothing -> return True Just digest -> do- let (Header pt ver _) = hdr let new_hdr = Header pt ver (fromIntegral $ B.length $ cipherDataContent cdata) expected_digest <- makeDigest False new_hdr $ cipherDataContent cdata- if expected_digest == digest- then return True- else return False+ return (expected_digest `bytesEq` digest) -- check if the padding is filled with the correct pattern if it exists paddingValid <- case cipherDataPadding cdata of Nothing -> return True Just pad -> do- ver <- stVersion <$> get+ cver <- stVersion <$> get let b = B.length pad - 1- if ver < TLS10- then return True- else return $ maybe True (const False) $ B.find (/= fromIntegral b) pad+ return (if cver < TLS10 then True else B.replicate (B.length pad) (fromIntegral b) `bytesEq` pad) - unless (and $! [ macValid, paddingValid ]) $ do+ unless (macValid &&! paddingValid) $ do throwError $ Error_Protocol ("bad record mac", True, BadRecordMac) return $ cipherDataContent cdata -decryptData :: EncryptedData -> TLSSt CipherData-decryptData (EncryptedData econtent) = do+decryptData :: Bytes -> TLSSt CipherData+decryptData econtent = do st <- get - let cipher = fromJust "cipher" $ stCipher st- let cst = fromJust "rx crypt state" $ stRxCryptState st- let padding_size = fromIntegral $ cipherPaddingSize cipher- let digestSize = fromIntegral $ cipherDigestSize cipher- let writekey = cstKey cst+ let cipher = fromJust "cipher" $ stCipher st+ let bulk = cipherBulk cipher+ let cst = fromJust "rx crypt state" $ stRxCryptState st+ let digestSize = hashSize $ cipherHash cipher+ let writekey = cstKey cst - case cipherF cipher of- CipherNoneF -> do+ case bulkF bulk of+ BulkNoneF -> do let contentlen = B.length econtent - digestSize case partition3 econtent (contentlen, digestSize, 0) of Nothing ->@@ -216,13 +218,13 @@ , cipherDataMAC = Just mac , cipherDataPadding = Nothing }- CipherBlockF _ decryptF -> do+ BulkBlockF _ decryptF -> do {- update IV -} let (iv, econtent') = if hasExplicitBlockIV $ stVersion st- then B.splitAt (fromIntegral $ cipherIVSize cipher) econtent+ then B.splitAt (bulkIVSize bulk) econtent else (cstIV cst, econtent)- let newiv = fromJust "new iv" $ takelast padding_size econtent'+ let newiv = fromJust "new iv" $ takelast (bulkBlockSize bulk) econtent' put $ st { stRxCryptState = Just $ cst { cstIV = newiv } } let content' = decryptF writekey iv econtent'@@ -234,7 +236,7 @@ , cipherDataMAC = Just mac , cipherDataPadding = Just padding }- CipherStreamF initF _ decryptF -> do+ BulkStreamF initF _ decryptF -> do let iv = cstIV cst let (content', newiv) = decryptF (if iv /= B.empty then iv else initF writekey) econtent {- update Ctx -}
+ Network/TLS/Record.hs view
@@ -0,0 +1,96 @@+{-# LANGUAGE EmptyDataDecls #-}+-- |+-- Module : Network.TLS.Record+-- License : BSD-style+-- Maintainer : Vincent Hanquez <vincent@snarc.org>+-- Stability : experimental+-- Portability : unknown+--+-- The Record Protocol takes messages to be transmitted, fragments the+-- data into manageable blocks, optionally compresses the data, applies+-- a MAC, encrypts, and transmits the result. Received data is+-- decrypted, verified, decompressed, reassembled, and then delivered to+-- higher-level clients.+--+module Network.TLS.Record+ ( Header(..)+ , ProtocolType(..)+ , packetType+ -- * TLS Records+ , Record(..)+ -- * TLS Record fragment and constructors+ , Fragment+ , fragmentPlaintext+ , fragmentCiphertext+ , fragmentGetBytes+ , Plaintext+ , Compressed+ , Ciphertext+ -- * manipulate record+ , onRecordFragment+ , fragmentCompress+ , fragmentCipher+ , fragmentUncipher+ , fragmentUncompress+ -- * serialize record+ , rawToRecord+ , recordToRaw+ , recordToHeader+ ) where++import Network.TLS.Struct+import Network.TLS.State+import qualified Data.ByteString as B+import Control.Applicative ((<$>))++-- | Represent a TLS record.+data Record a = Record !ProtocolType !Version !(Fragment a) deriving (Show,Eq)++newtype Fragment a = Fragment Bytes deriving (Show,Eq)++data Plaintext+data Compressed+data Ciphertext++fragmentPlaintext :: Bytes -> Fragment Plaintext+fragmentPlaintext bytes = Fragment bytes++fragmentCiphertext :: Bytes -> Fragment Ciphertext+fragmentCiphertext bytes = Fragment bytes++fragmentGetBytes :: Fragment a -> Bytes+fragmentGetBytes (Fragment bytes) = bytes++onRecordFragment :: Record a -> (Fragment a -> TLSSt (Fragment b)) -> TLSSt (Record b)+onRecordFragment (Record pt ver frag) f = Record pt ver <$> f frag++fragmentMap :: (Bytes -> TLSSt Bytes) -> Fragment a -> TLSSt (Fragment b)+fragmentMap f (Fragment b) = Fragment <$> f b++-- | turn a plaintext record into a compressed record using the compression function supplied+fragmentCompress :: (Bytes -> TLSSt Bytes) -> Fragment Plaintext -> TLSSt (Fragment Compressed)+fragmentCompress f = fragmentMap f++-- | turn a compressed record into a ciphertext record using the cipher function supplied+fragmentCipher :: (Bytes -> TLSSt Bytes) -> Fragment Compressed -> TLSSt (Fragment Ciphertext)+fragmentCipher f = fragmentMap f++-- | turn a ciphertext fragment into a compressed fragment using the cipher function supplied+fragmentUncipher :: (Bytes -> TLSSt Bytes) -> Fragment Ciphertext -> TLSSt (Fragment Compressed)+fragmentUncipher f = fragmentMap f++-- | turn a compressed fragment into a plaintext fragment using the decompression function supplied+fragmentUncompress :: (Bytes -> TLSSt Bytes) -> Fragment Compressed -> TLSSt (Fragment Plaintext)+fragmentUncompress f = fragmentMap f++-- | turn a record into an header and bytes+recordToRaw :: Record a -> (Header, Bytes)+recordToRaw (Record pt ver (Fragment bytes)) = (Header pt ver (fromIntegral $ B.length bytes), bytes)++-- | turn a header and a fragment into a record+rawToRecord :: Header -> Fragment a -> Record a+rawToRecord (Header pt ver _) fragment = Record pt ver fragment++-- | turn a record into a header+recordToHeader :: Record a -> Header+recordToHeader (Record pt ver (Fragment bytes)) = Header pt ver (fromIntegral $ B.length bytes)
Network/TLS/Sending.hs view
@@ -12,6 +12,7 @@ writePacket ) where +import Control.Applicative ((<$>)) import Control.Monad.State import Data.ByteString (ByteString)@@ -21,57 +22,63 @@ import Network.TLS.Cap import Network.TLS.Wire import Network.TLS.Struct+import Network.TLS.Record import Network.TLS.Packet import Network.TLS.State import Network.TLS.Cipher+import Network.TLS.Compression import Network.TLS.Crypto {- - 'makePacketData' create a Header and a content bytestring related to a packet - this doesn't change any state -}-makePacketData :: Packet -> TLSSt (Header, ByteString)-makePacketData pkt = do- ver <- get >>= return . stVersion+makeRecord :: Packet -> TLSSt (Record Plaintext)+makeRecord pkt = do+ ver <- stVersion <$> get content <- writePacketContent pkt- let hdr = Header (packetType pkt) ver (fromIntegral $ B.length content)- return (hdr, content)+ return $ Record (packetType pkt) ver (fragmentPlaintext content) {- - Handshake data need to update a digest -}-processPacketData :: (Header, ByteString) -> TLSSt (Header, ByteString)-processPacketData dat@(Header ty _ _, content) = do- when (ty == ProtocolType_Handshake) (updateHandshakeDigest content)- return dat+processRecord :: Record Plaintext -> TLSSt (Record Plaintext)+processRecord record@(Record ty _ fragment) = do+ when (ty == ProtocolType_Handshake) (updateHandshakeDigest $ fragmentGetBytes fragment)+ return record +compressRecord :: Record Plaintext -> TLSSt (Record Compressed)+compressRecord record =+ onRecordFragment record $ fragmentCompress $ \bytes -> do+ withCompression $ compressionDeflate bytes+ {- - when Tx Encrypted is set, we pass the data through encryptContent, otherwise - we just return the packet -}-encryptPacketData :: (Header, ByteString) -> TLSSt (Header, ByteString)-encryptPacketData dat = do+encryptRecord :: Record Compressed -> TLSSt (Record Ciphertext)+encryptRecord record = onRecordFragment record $ fragmentCipher $ \bytes -> do st <- get if stTxEncrypted st- then encryptContent dat- else return dat+ then encryptContent record bytes+ else return bytes {- - ChangeCipherSpec state change need to be handled after encryption otherwise - its own packet would be encrypted with the new context, instead of beeing sent - under the current context -}-postprocessPacketData :: (Header, ByteString) -> TLSSt (Header, ByteString)-postprocessPacketData dat@(Header ProtocolType_ChangeCipherSpec _ _, _) =- switchTxEncryption >> isClientContext >>= \cc -> when cc setKeyBlock >> return dat--postprocessPacketData dat = return dat+postprocessRecord :: Record Ciphertext -> TLSSt (Record Ciphertext)+postprocessRecord record@(Record ProtocolType_ChangeCipherSpec _ _) =+ switchTxEncryption >> isClientContext >>= \cc -> when cc setKeyBlock >> return record+postprocessRecord record = return record {- - marshall packet data -}-encodePacket :: (Header, ByteString) -> TLSSt ByteString-encodePacket (hdr, content) = return $ B.concat [ encodeHeader hdr, content ]+encodeRecord :: Record Ciphertext -> TLSSt ByteString+encodeRecord record = return $ B.concat [ encodeHeader hdr, content ]+ where (hdr, content) = recordToRaw record {- - just update TLS state machine@@ -92,8 +99,9 @@ - and updating state on the go -} writePacket :: Packet -> TLSSt ByteString-writePacket pkt = preProcessPacket pkt >> makePacketData pkt >>= processPacketData >>=- encryptPacketData >>= postprocessPacketData >>= encodePacket+writePacket pkt = do+ preProcessPacket pkt+ makeRecord pkt >>= processRecord >>= compressRecord >>= encryptRecord >>= postprocessRecord >>= encodeRecord {------------------------------------------------------------------------------} {- SENDING Helpers -}@@ -110,46 +118,46 @@ Left err -> fail ("rsa encrypt failed: " ++ show err) Right (econtent, rng') -> put (st { stRandomGen = rng' }) >> return econtent -encryptContent :: (Header, ByteString) -> TLSSt (Header, ByteString)-encryptContent (hdr@(Header pt ver _), content) = do- digest <- makeDigest True hdr content- encrypted_msg <- encryptData $ B.concat [content, digest]- let hdrnew = Header pt ver (fromIntegral $ B.length encrypted_msg)- return (hdrnew, encrypted_msg)+encryptContent :: Record Compressed -> ByteString -> TLSSt ByteString+encryptContent record content = do+ digest <- makeDigest True (recordToHeader record) content+ encryptData $ B.concat [content, digest] encryptData :: ByteString -> TLSSt ByteString encryptData content = do st <- get let cipher = fromJust "cipher" $ stCipher st+ let bulk = cipherBulk cipher let cst = fromJust "tx crypt state" $ stTxCryptState st- let padding_size = fromIntegral $ cipherPaddingSize cipher - let msg_len = B.length content- let padding = if padding_size > 0- then- let padbyte = padding_size - (msg_len `mod` padding_size) in- let padbyte' = if padbyte == 0 then padding_size else padbyte in- B.replicate padbyte' (fromIntegral (padbyte' - 1))- else- B.empty let writekey = cstKey cst - case cipherF cipher of- CipherNoneF -> return content- CipherBlockF encrypt _ -> do+ case bulkF bulk of+ BulkNoneF -> return content+ BulkBlockF encrypt _ -> do+ let blockSize = fromIntegral $ bulkBlockSize bulk+ let msg_len = B.length content+ let padding = if blockSize > 0+ then+ let padbyte = blockSize - (msg_len `mod` blockSize) in+ let padbyte' = if padbyte == 0 then blockSize else padbyte in+ B.replicate padbyte' (fromIntegral (padbyte' - 1))+ else+ B.empty+ -- before TLS 1.1, the block cipher IV is made of the residual of the previous block. iv <- if hasExplicitBlockIV $ stVersion st- then genTLSRandom (fromIntegral $ cipherIVSize cipher)+ then genTLSRandom (bulkIVSize bulk) else return $ cstIV cst let e = encrypt writekey iv (B.concat [ content, padding ]) if hasExplicitBlockIV $ stVersion st then return $ B.concat [iv,e] else do- let newiv = fromJust "new iv" $ takelast (fromIntegral $ cipherIVSize cipher) e+ let newiv = fromJust "new iv" $ takelast (bulkIVSize bulk) e put $ st { stTxCryptState = Just $ cst { cstIV = newiv } } return e- CipherStreamF initF encryptF _ -> do+ BulkStreamF initF encryptF _ -> do let iv = cstIV cst let (e, newiv) = encryptF (if iv /= B.empty then iv else initF writekey) content put $ st { stTxCryptState = Just $ cst { cstIV = newiv } }
Network/TLS/State.hs view
@@ -21,6 +21,7 @@ , newTLSState , genTLSRandom , withTLSRNG+ , withCompression , assert -- FIXME move somewhere else (Internal.hs ?) , updateStatusHs , updateStatusCC@@ -58,6 +59,7 @@ import Network.TLS.Packet import Network.TLS.Crypto import Network.TLS.Cipher+import Network.TLS.Compression import Network.TLS.MAC import qualified Data.ByteString as B import Control.Applicative ((<$>))@@ -109,7 +111,7 @@ , hstMasterSecret :: !(Maybe Bytes) , hstRSAPublicKey :: !(Maybe PublicKey) , hstRSAPrivateKey :: !(Maybe PrivateKey)- , hstHandshakeDigest :: Maybe (HashCtx, HashCtx) -- FIXME could be only 1 hash in tls12+ , hstHandshakeDigest :: !HashCtx } deriving (Show) data StateRNG = forall g . CryptoRandomGen g => StateRNG g@@ -129,6 +131,7 @@ , stTxMacState :: !(Maybe TLSMacState) , stRxMacState :: !(Maybe TLSMacState) , stCipher :: Maybe Cipher+ , stCompression :: Compression , stRandomGen :: StateRNG , stSecureRenegotiation :: Bool -- RFC 5746 , stClientVerifiedData :: Bytes -- RFC 5746@@ -161,6 +164,7 @@ , stTxMacState = Nothing , stRxMacState = Nothing , stCipher = Nothing+ , stCompression = nullCompression , stRandomGen = StateRNG rng , stSecureRenegotiation = False , stClientVerifiedData = B.empty@@ -172,6 +176,13 @@ Left err -> Left err Right (a, rng') -> Right (a, StateRNG rng') +withCompression :: (Compression -> (Compression, a)) -> TLSSt a+withCompression f = do+ compression <- stCompression <$> get+ let (nc, a) = f compression+ modify (\st -> st { stCompression = nc })+ return a+ genTLSRandom :: (MonadState TLSState m, MonadError TLSError m) => Int -> m Bytes genTLSRandom n = do st <- get@@ -186,12 +197,12 @@ let cst = fromJust "crypt state" $ if w then stTxCryptState st else stRxCryptState st let ms = fromJust "mac state" $ if w then stTxMacState st else stRxMacState st let cipher = fromJust "cipher" $ stCipher st- let machash = cipherMACHash cipher+ let hashf = hashF $ cipherHash cipher let (macF, msg) = if ver < TLS10- then (macSSL machash, B.concat [ encodeWord64 $ msSequence ms, encodeHeaderNoVer hdr, content ])- else (hmac machash 64, B.concat [ encodeWord64 $ msSequence ms, encodeHeader hdr, content ])+ then (macSSL hashf, B.concat [ encodeWord64 $ msSequence ms, encodeHeaderNoVer hdr, content ])+ else (hmac hashf 64, B.concat [ encodeWord64 $ msSequence ms, encodeHeader hdr, content ]) let digest = macF (cstMacSecret cst) msg let newms = ms { msSequence = (msSequence ms) + 1 }@@ -314,10 +325,12 @@ let cc = stClientContext st let cipher = fromJust "cipher" $ stCipher st- let keyblockSize = fromIntegral $ cipherKeyBlockSize cipher- let digestSize = fromIntegral $ cipherDigestSize cipher- let keySize = fromIntegral $ cipherKeySize cipher- let ivSize = fromIntegral $ cipherIVSize cipher+ let keyblockSize = cipherKeyBlockSize cipher++ let bulk = cipherBulk cipher+ let digestSize = hashSize $ cipherHash cipher+ let keySize = bulkKeySize bulk+ let ivSize = bulkIVSize bulk let kb = generateKeyBlock (stVersion st) (hstClientRandom hst) (fromJust "server random" $ hstServerRandom hst) (fromJust "master secret" $ hstMasterSecret hst) keyblockSize@@ -364,23 +377,24 @@ isClientContext = get >>= return . stClientContext -- create a new empty handshake state-newEmptyHandshake :: Version -> ClientRandom -> TLSHandshakeState-newEmptyHandshake ver crand = TLSHandshakeState+newEmptyHandshake :: Version -> ClientRandom -> HashCtx -> TLSHandshakeState+newEmptyHandshake ver crand digestInit = TLSHandshakeState { hstClientVersion = ver , hstClientRandom = crand , hstServerRandom = Nothing , hstMasterSecret = Nothing , hstRSAPublicKey = Nothing , hstRSAPrivateKey = Nothing- , hstHandshakeDigest = Nothing+ , hstHandshakeDigest = digestInit } startHandshakeClient :: MonadState TLSState m => Version -> ClientRandom -> m () startHandshakeClient ver crand = do -- FIXME check if handshake is already not null+ let initCtx = if ver < TLS12 then hashMD5SHA1 else hashSHA256 chs <- get >>= return . stHandshake when (isNothing chs) $- modify (\st -> st { stHandshake = Just $ newEmptyHandshake ver crand })+ modify (\st -> st { stHandshake = Just $ newEmptyHandshake ver crand initCtx }) hasValidHandshake :: MonadState TLSState m => String -> m () hasValidHandshake name = get >>= \st -> assert name [ ("valid handshake", isNothing $ stHandshake st) ]@@ -391,14 +405,8 @@ modify (\st -> st { stHandshake = f <$> stHandshake st }) updateHandshakeDigest :: MonadState TLSState m => Bytes -> m ()-updateHandshakeDigest content = updateHandshake "update digest" (\hs ->- let (c1, c2) = case hstHandshakeDigest hs of- Nothing -> (initHash HashTypeSHA1, initHash HashTypeMD5)- Just (sha1ctx, md5ctx) -> (sha1ctx, md5ctx) in- let nc1 = updateHash c1 content in- let nc2 = updateHash c2 content in- hs { hstHandshakeDigest = Just (nc1, nc2) }- )+updateHandshakeDigest content = updateHandshake "update digest" $ \hs ->+ hs { hstHandshakeDigest = hashUpdate (hstHandshakeDigest hs) content } updateHandshakeDigestSplitted :: MonadState TLSState m => HandshakeType -> Bytes -> m () updateHandshakeDigestSplitted ty bytes = updateHandshakeDigest $ B.concat [hdr, bytes]@@ -409,9 +417,9 @@ getHandshakeDigest client = do st <- get let hst = fromJust "handshake" $ stHandshake st- let (sha1ctx, md5ctx) = fromJust "handshake digest" $ hstHandshakeDigest hst- let msecret = fromJust "master secret" $ hstMasterSecret hst- return $ (if client then generateClientFinished else generateServerFinished) (stVersion st) msecret md5ctx sha1ctx+ let hashctx = hstHandshakeDigest hst+ let msecret = fromJust "master secret" $ hstMasterSecret hst+ return $ (if client then generateClientFinished else generateServerFinished) (stVersion st) msecret hashctx endHandshake :: MonadState TLSState m => m () endHandshake = modify (\st -> st { stHandshake = Nothing })
Network/TLS/Struct.hs view
@@ -16,7 +16,6 @@ , CipherType(..) , CipherData(..) , Extension- , EncryptedData(..) , CertificateType(..) , HashAlgorithm(..) , SignatureAlgorithm(..)@@ -48,7 +47,7 @@ import Data.ByteString (ByteString) import qualified Data.ByteString as B (length) import Data.Word-import Data.Certificate.X509+import Data.Certificate.X509 (X509) import Data.Typeable import Control.Monad.Error (Error(..)) import Control.Exception (Exception(..))@@ -137,7 +136,7 @@ | AppData ByteString deriving (Show,Eq) -data Header = Header ProtocolType Version Word16 deriving (Show, Eq)+data Header = Header ProtocolType Version Word16 deriving (Show,Eq) newtype ServerRandom = ServerRandom Bytes deriving (Show, Eq) newtype ClientRandom = ClientRandom Bytes deriving (Show, Eq)@@ -156,9 +155,6 @@ clientRandom :: Bytes -> Maybe ClientRandom clientRandom l = constrRandom32 ClientRandom l--newtype EncryptedData = EncryptedData ByteString- deriving (Show) data AlertLevel = AlertLevel_Warning
Network/TLS/Util.hs view
@@ -4,8 +4,12 @@ , partition3 , partition6 , fromJust+ , and'+ , (&&!)+ , bytesEq ) where +import Data.List (foldl') import Network.TLS.Struct (Bytes) import qualified Data.ByteString as B @@ -41,3 +45,20 @@ fromJust :: String -> Maybe a -> a fromJust what Nothing = error ("fromJust " ++ what ++ ": Nothing") -- yuck fromJust _ (Just x) = x++-- | This is a strict version of and+and' :: [Bool] -> Bool+and' l = foldl' (&&!) True l++-- | This is a strict version of &&.+(&&!) :: Bool -> Bool -> Bool+True &&! True = True+True &&! False = False+False &&! True = False+False &&! False = False++-- | verify that 2 bytestrings are equals.+-- it's a non lazy version, that will compare every bytes.+-- arguments need to be of same length+bytesEq :: Bytes -> Bytes -> Bool+bytesEq b1 = and' . B.zipWith (==) b1
Network/TLS/Wire.hs view
@@ -40,6 +40,7 @@ import Data.Bits import Network.TLS.Struct +runGet :: String -> Get a -> Bytes -> Either String a runGet lbl f = G.runGet (label lbl f) getWords8 :: Get [Word8]
− README
@@ -1,7 +0,0 @@-The hs-tls project aims to reimplement the full TLS protocol (formely known as SSL) in haskell.-The focus of the projects is to provide a safer implementation than the ones existing,-through more purity, more type-checking, and more units tests.--While the focus is to make it safer than other implementations, this current-implementation is *not* to be considered secure, since it doesn't fully-implement everything necessary (full certificate checking, protocol requirements, etc)
+ README.md view
@@ -0,0 +1,25 @@+haskell TLS+===========++This library provide native Haskell TLS and SSL protocol implementation for server and client.++Description+-----------++This provides a high-level implementation of a sensitive security protocol,+eliminating a common set of security issues through the use of the advanced+type system, high level constructions and common Haskell features.++Only core protocol available here, have a look at the tls-extra package for+default ciphers, compressions and certificates functions.++Features+--------++* tiny code base (more than 20 times smaller than openSSL, and 10 times smaller than gnuTLS)+* permissive license: BSD3.+* supported versions: SSL3, TLS1.0, TLS1.1, TLS1.2.+* key exchange supported: only RSA.+* bulk algorithm supported: any stream or block ciphers.+* supported extensions: secure renegociation+
TODO view
@@ -4,17 +4,9 @@ - add Client Certificates - process session as they should - put 4 bytes of time in client/server random-- implement compression - proper separation for key exchange algorithm (hardcoded to RSA at the moment in differents place) - implements different key exchange algorithm--tls v1.2:--- implement finish digest generation with hmac256-- implement finish digest generation with client/server negociated algorithm-- proper version dispatch in marshalling packets-- properly separate different version of the protocol-- implement AEAD+- implement AEAD bulk algorithm (TLS1.2) code cleanup: @@ -28,7 +20,6 @@ misc: -- stunnel: actually make it works like stunnel instead of hardcoding the data received/sent - investigate an iteratee/enumerator interface - portability - implement more ciphers
tls.cabal view
@@ -1,5 +1,5 @@ Name: tls-Version: 0.7.2+Version: 0.8.0 Description: Native Haskell TLS and SSL protocol implementation for server and client. .@@ -7,7 +7,7 @@ eliminating a common set of security issues through the use of the advanced type system, high level constructions and common Haskell features. .- Currently implement the SSL3.0, TLS1.0 and TLS1.1 protocol,+ Currently implement the SSL3.0, TLS1.0, TLS1.1 and TLS1.2 protocol, with only RSA supported for Key Exchange. . Only core protocol available here, have a look at the@@ -24,7 +24,7 @@ stability: experimental Cabal-Version: >=1.6 Homepage: http://github.com/vincenthz/hs-tls-data-files: README, TODO+data-files: README.md, TODO Flag test Description: Build unit test@@ -42,7 +42,7 @@ , bytestring , crypto-api >= 0.5 , cryptocipher >= 0.2.5- , certificate >= 0.9 && < 1.0+ , certificate >= 0.9.3 && < 1.0 Exposed-modules: Network.TLS Network.TLS.Cipher Network.TLS.Compression@@ -53,6 +53,7 @@ Network.TLS.Core Network.TLS.Crypto Network.TLS.Packet+ Network.TLS.Record Network.TLS.State Network.TLS.Sending Network.TLS.Receiving