initial import

This commit is contained in:
Vincent Hanquez 2010-09-09 22:47:19 +01:00
commit 0b5a0dc548
20 changed files with 2892 additions and 0 deletions

27
LICENSE Normal file
View file

@ -0,0 +1,27 @@
Copyright (c) 2010 Vincent Hanquez <vincent@snarc.org>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the author nor the names of his contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.

268
Network/TLS/Cipher.hs Normal file
View file

@ -0,0 +1,268 @@
-- |
-- Module : Network.TLS.Cipher
-- License : BSD-style
-- Maintainer : Vincent Hanquez <vincent@snarc.org>
-- Stability : experimental
-- Portability : unknown
--
module Network.TLS.Cipher
( CipherTypeFunctions(..)
, CipherKeyExchangeType(..)
, Cipher(..)
, cipherExchangeNeedMoreData
-- * builtin ciphers for ease of use, might move later to a tls-ciphers library
, cipher_null_null
, cipher_RC4_128_MD5
, cipher_RC4_128_SHA1
, cipher_AES128_SHA1
, cipher_AES256_SHA1
, cipher_AES128_SHA256
, cipher_AES256_SHA256
) where
import Data.Word
import Network.TLS.Struct (Version(..))
import Network.TLS.MAC
import qualified Data.Vector.Unboxed as Vector (fromList, toList)
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString as B
import qualified Codec.Crypto.AES as AES
import qualified Crypto.Cipher.RC4 as RC4
-- FIXME convert to newtype
type Key = B.ByteString
type IV = B.ByteString
data CipherTypeFunctions =
CipherNoneF -- special value for 0
| CipherBlockF (Key -> IV -> L.ByteString -> L.ByteString)
(Key -> IV -> L.ByteString -> L.ByteString)
| CipherStreamF (Key -> IV)
(IV -> L.ByteString -> (L.ByteString, IV))
(IV -> L.ByteString -> (L.ByteString, IV))
data CipherKeyExchangeType =
CipherKeyExchangeRSA
| CipherKeyExchangeDHE_RSA
| CipherKeyExchangeECDHE_RSA
| CipherKeyExchangeDHE_DSS
| CipherKeyExchangeDH_DSS
| CipherKeyExchangeDH_RSA
| CipherKeyExchangeECDH_ECDSA
| CipherKeyExchangeECDH_RSA
| CipherKeyExchangeECDHE_ECDSA
data Cipher = Cipher
{ cipherID :: Word16
, cipherName :: String
, cipherDigestSize :: Word8
, cipherKeySize :: Word8
, cipherIVSize :: Word8
, cipherKeyBlockSize :: Word8
, cipherPaddingSize :: Word8
, cipherKeyExchange :: CipherKeyExchangeType
, cipherHMAC :: L.ByteString -> L.ByteString -> L.ByteString
, cipherF :: CipherTypeFunctions
, cipherMinVer :: Maybe Version
}
instance Show Cipher where
show c = cipherName c
cipherExchangeNeedMoreData :: CipherKeyExchangeType -> Bool
cipherExchangeNeedMoreData CipherKeyExchangeRSA = False
cipherExchangeNeedMoreData CipherKeyExchangeDHE_RSA = True
cipherExchangeNeedMoreData CipherKeyExchangeECDHE_RSA = True
cipherExchangeNeedMoreData CipherKeyExchangeDHE_DSS = True
cipherExchangeNeedMoreData CipherKeyExchangeDH_DSS = False
cipherExchangeNeedMoreData CipherKeyExchangeDH_RSA = False
cipherExchangeNeedMoreData CipherKeyExchangeECDH_ECDSA = True
cipherExchangeNeedMoreData CipherKeyExchangeECDH_RSA = True
cipherExchangeNeedMoreData CipherKeyExchangeECDHE_ECDSA = True
repack :: Int -> L.ByteString -> [B.ByteString]
repack bs x =
if L.length x > fromIntegral bs
then
let (c1, c2) = L.splitAt (fromIntegral bs) x in
B.pack (L.unpack c1) : repack 16 c2
else
[ B.pack (L.unpack x) ]
aes128_cbc_encrypt :: Key -> IV -> L.ByteString -> L.ByteString
aes128_cbc_encrypt key iv d = AES.crypt AES.CBC key iv AES.Encrypt d16
where d16 = L.fromChunks $ repack 16 d
aes128_cbc_decrypt :: Key -> IV -> L.ByteString -> L.ByteString
aes128_cbc_decrypt key iv d = AES.crypt AES.CBC key iv AES.Decrypt d16
where d16 = L.fromChunks $ repack 16 d
aes256_cbc_encrypt :: Key -> IV -> L.ByteString -> L.ByteString
aes256_cbc_encrypt key iv d = AES.crypt AES.CBC key iv AES.Encrypt d16
where d16 = L.fromChunks $ repack 16 d
aes256_cbc_decrypt :: Key -> IV -> L.ByteString -> L.ByteString
aes256_cbc_decrypt key iv d = AES.crypt AES.CBC key iv AES.Decrypt d16
where d16 = L.fromChunks $ repack 32 d
toIV :: RC4.Ctx -> IV
toIV (v, x, y) = B.pack (x : y : Vector.toList v)
toCtx :: IV -> RC4.Ctx
toCtx iv =
case B.unpack iv of
x:y:l -> (Vector.fromList l, x, y)
_ -> (Vector.fromList [], 0, 0)
initF_rc4 :: Key -> IV
initF_rc4 key = toIV $ RC4.initCtx (B.unpack key)
encryptF_rc4 :: IV -> L.ByteString -> (L.ByteString, IV)
encryptF_rc4 iv d = (\(ctx, e) -> (e, toIV ctx)) $ RC4.encryptlazy (toCtx iv) d
decryptF_rc4 :: IV -> L.ByteString -> (L.ByteString, IV)
decryptF_rc4 iv e = (\(ctx, d) -> (d, toIV ctx)) $ RC4.decryptlazy (toCtx iv) e
{-
TLS 1.0 ciphers definition
CipherSuite TLS_NULL_WITH_NULL_NULL = { 0x00,0x00 };
CipherSuite TLS_RSA_WITH_NULL_MD5 = { 0x00,0x01 };
CipherSuite TLS_RSA_WITH_NULL_SHA = { 0x00,0x02 };
CipherSuite TLS_RSA_EXPORT_WITH_RC4_40_MD5 = { 0x00,0x03 };
CipherSuite TLS_RSA_WITH_RC4_128_MD5 = { 0x00,0x04 };
CipherSuite TLS_RSA_WITH_RC4_128_SHA = { 0x00,0x05 };
CipherSuite TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 = { 0x00,0x06 };
CipherSuite TLS_RSA_WITH_IDEA_CBC_SHA = { 0x00,0x07 };
CipherSuite TLS_RSA_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x08 };
CipherSuite TLS_RSA_WITH_DES_CBC_SHA = { 0x00,0x09 };
CipherSuite TLS_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00,0x0A };
CipherSuite TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x0B };
CipherSuite TLS_DH_DSS_WITH_DES_CBC_SHA = { 0x00,0x0C };
CipherSuite TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA = { 0x00,0x0D };
CipherSuite TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x0E };
CipherSuite TLS_DH_RSA_WITH_DES_CBC_SHA = { 0x00,0x0F };
CipherSuite TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00,0x10 };
CipherSuite TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x11 };
CipherSuite TLS_DHE_DSS_WITH_DES_CBC_SHA = { 0x00,0x12 };
CipherSuite TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = { 0x00,0x13 };
CipherSuite TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x14 };
CipherSuite TLS_DHE_RSA_WITH_DES_CBC_SHA = { 0x00,0x15 };
CipherSuite TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00,0x16 };
CipherSuite TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 = { 0x00,0x17 };
CipherSuite TLS_DH_anon_WITH_RC4_128_MD5 = { 0x00,0x18 };
CipherSuite TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x19 };
CipherSuite TLS_DH_anon_WITH_DES_CBC_SHA = { 0x00,0x1A };
CipherSuite TLS_DH_anon_WITH_3DES_EDE_CBC_SHA = { 0x00,0x1B };
-}
{-
- some builtin ciphers description
-}
cipher_null_null :: Cipher
cipher_null_null = Cipher
{ cipherID = 0x0
, cipherName = "null-null"
, cipherDigestSize = 0
, cipherKeySize = 0
, cipherIVSize = 0
, cipherKeyBlockSize = 0
, cipherPaddingSize = 0
, cipherHMAC = (\_ _ -> L.empty)
, cipherKeyExchange = CipherKeyExchangeRSA
, cipherF = CipherNoneF
, cipherMinVer = Nothing
}
cipher_RC4_128_MD5 :: Cipher
cipher_RC4_128_MD5 = Cipher
{ cipherID = 0x04
, cipherName = "RSA-rc4-128-md5"
, cipherDigestSize = 16
, cipherKeySize = 16
, cipherIVSize = 0
, cipherKeyBlockSize = 2 * (16 + 16 + 0)
, cipherPaddingSize = 0
, cipherHMAC = hmacMD5
, cipherKeyExchange = CipherKeyExchangeRSA
, cipherF = CipherStreamF initF_rc4 encryptF_rc4 decryptF_rc4
, cipherMinVer = Nothing
}
cipher_RC4_128_SHA1 :: Cipher
cipher_RC4_128_SHA1 = Cipher
{ cipherID = 0x05
, cipherName = "RSA-rc4-128-sha1"
, cipherDigestSize = 20
, cipherKeySize = 16
, cipherIVSize = 0
, cipherKeyBlockSize = 2 * (20 + 16 + 0)
, cipherPaddingSize = 0
, cipherHMAC = hmacSHA1
, cipherKeyExchange = CipherKeyExchangeRSA
, cipherF = CipherStreamF initF_rc4 encryptF_rc4 decryptF_rc4
, cipherMinVer = Nothing
}
cipher_AES128_SHA1 :: Cipher
cipher_AES128_SHA1 = Cipher
{ cipherID = 0x2f
, cipherName = "RSA-aes128-sha1"
, cipherDigestSize = 20
, cipherKeySize = 16
, cipherIVSize = 16
, cipherKeyBlockSize = 2 * (20 + 16 + 16)
, cipherPaddingSize = 16
, cipherHMAC = hmacSHA1
, cipherKeyExchange = CipherKeyExchangeRSA
, cipherF = CipherBlockF aes128_cbc_encrypt aes128_cbc_decrypt
, cipherMinVer = Just SSL3
}
cipher_AES256_SHA1 :: Cipher
cipher_AES256_SHA1 = Cipher
{ cipherID = 0x35
, cipherName = "RSA-aes256-sha1"
, cipherDigestSize = 20
, cipherKeySize = 32
, cipherIVSize = 16
, cipherKeyBlockSize = 2 * (20 + 32 + 16)
, cipherPaddingSize = 16
, cipherHMAC = hmacSHA1
, cipherKeyExchange = CipherKeyExchangeRSA
, cipherF = CipherBlockF aes256_cbc_encrypt aes256_cbc_decrypt
, cipherMinVer = Just SSL3
}
cipher_AES128_SHA256 :: Cipher
cipher_AES128_SHA256 = Cipher
{ cipherID = 0x3c
, cipherName = "RSA-aes128-sha256"
, cipherDigestSize = 32
, cipherKeySize = 16
, cipherIVSize = 16
, cipherKeyBlockSize = 2 * (32 + 16 + 16)
, cipherPaddingSize = 16
, cipherHMAC = hmacSHA256
, cipherKeyExchange = CipherKeyExchangeRSA
, cipherF = CipherBlockF aes128_cbc_encrypt aes128_cbc_decrypt
, cipherMinVer = Just TLS12
}
cipher_AES256_SHA256 :: Cipher
cipher_AES256_SHA256 = Cipher
{ cipherID = 0x3d
, cipherName = "RSA-aes256-sha256"
, cipherDigestSize = 32
, cipherKeySize = 32
, cipherIVSize = 16
, cipherKeyBlockSize = 2 * (32 + 32 + 16)
, cipherPaddingSize = 16
, cipherHMAC = hmacSHA256
, cipherKeyExchange = CipherKeyExchangeRSA
, cipherF = CipherBlockF aes256_cbc_encrypt aes256_cbc_decrypt
, cipherMinVer = Just TLS12
}

207
Network/TLS/Client.hs Normal file
View file

@ -0,0 +1,207 @@
{-# LANGUAGE GeneralizedNewtypeDeriving, MultiParamTypeClasses #-}
-- |
-- Module : Network.TLS.Client
-- License : BSD-style
-- Maintainer : Vincent Hanquez <vincent@snarc.org>
-- Stability : experimental
-- Portability : unknown
--
-- the Client module contains the necessary calls to create a connecting TLS socket
-- aka. a client socket.
--
module Network.TLS.Client
( TLSClientParams(..)
, TLSStateClient
, runTLSClient
-- * low level packet sending receiving.
, recvPacket
, sendPacket
-- * API, warning probably subject to change
, connect
, sendData
, recvData
, close
) where
import Data.Maybe
import Data.Word
import Control.Monad.Trans
import Control.Monad.State
import Data.Certificate.X509
import Network.TLS.Cipher
import Network.TLS.Struct
import Network.TLS.Packet
import Network.TLS.State
import Network.TLS.Sending
import Network.TLS.Receiving
import Network.TLS.SRandom
import qualified Data.ByteString.Lazy as L
import System.IO (Handle, hFlush)
import Data.List (find)
data TLSClientParams = TLSClientParams
{ cpConnectVersion :: Version -- ^ client version we're sending by default
, cpAllowedVersions :: [Version] -- ^ allowed versions from the server
, cpSession :: Maybe [Word8] -- ^ session for this connection
, cpCiphers :: [Cipher] -- ^ all ciphers for this connection
, cpCertificate :: Maybe Certificate -- ^ an optional client certificate
} deriving (Show)
data TLSStateClient = TLSStateClient
{ scParams :: TLSClientParams -- ^ client params and config for this connection
, scTLSState :: TLSState -- ^ client TLS State for this connection
, scCertRequested :: Bool -- ^ mark that the server requested a certificate
} deriving (Show)
newtype TLSClient m a = TLSClient { runTLSC :: StateT TLSStateClient m a }
deriving (Monad, MonadState TLSStateClient)
instance Monad m => MonadTLSState (TLSClient m) where
getTLSState = TLSClient (get >>= return . scTLSState)
putTLSState s = TLSClient (get >>= put . (\st -> st { scTLSState = s }))
instance MonadTrans TLSClient where
lift = TLSClient . lift
instance Monad m => Functor (TLSClient m) where
fmap f = TLSClient . fmap f . runTLSC
runTLSClientST :: TLSClient m a -> TLSStateClient -> m (a, TLSStateClient)
runTLSClientST f s = runStateT (runTLSC f) s
runTLSClient :: TLSClient m a -> TLSClientParams -> SRandomGen -> m (a, TLSStateClient)
runTLSClient f params rng = runTLSClientST f (TLSStateClient { scParams = params, scTLSState = state, scCertRequested = False })
where state = (newTLSState rng) { stVersion = TLS10, stClientContext = True }
{- | receive a single TLS packet or on error a TLSError -}
recvPacket :: Handle -> TLSClient IO (Either TLSError Packet)
recvPacket handle = do
hdr <- lift $ L.hGet handle 5 >>= return . decodeHeader
case hdr of
Left err -> return $ Left err
Right header@(Header _ _ readlen) -> do
content <- lift $ L.hGet handle (fromIntegral readlen)
readPacket header (EncryptedData content)
{- | send a single TLS packet -}
sendPacket :: Handle -> Packet -> TLSClient IO ()
sendPacket handle pkt = do
dataToSend <- writePacket pkt
lift $ L.hPut handle dataToSend
recvServerHello :: Handle -> TLSClient IO ()
recvServerHello handle = do
ciphers <- fmap (cpCiphers . scParams) get
allowedvers <- fmap (cpAllowedVersions . scParams) get
pkt <- recvPacket handle
let hs = case pkt of
Right (Handshake h) -> h
Left err -> error ("error received: " ++ show err)
Right x -> error ("unexpected packet received, expecting handshake " ++ show x)
case hs of
ServerHello ver _ _ cipher _ _ -> do
case find ((==) ver) allowedvers of
Nothing -> error ("received version which is not allowed: " ++ show ver)
Just _ -> setVersion ver
case find ((==) cipher . cipherID) ciphers of
Nothing -> error "no cipher in common with the server"
Just c -> setCipher c
recvServerHello handle
CertRequest _ _ _ -> modify (\sc -> sc { scCertRequested = True }) >> recvServerHello handle
Certificates _ -> recvServerHello handle
ServerHelloDone -> return ()
_ -> error "unexpected handshake message received in server hello messages"
connectSendClientHello :: Handle -> ClientRandom -> TLSClient IO ()
connectSendClientHello handle crand = do
ver <- fmap (cpConnectVersion . scParams) get
ciphers <- fmap (cpCiphers . scParams) get
sendPacket handle $ Handshake (ClientHello ver crand (Session Nothing) (map cipherID ciphers) [ 0 ] Nothing)
connectSendClientCertificate :: Handle -> TLSClient IO ()
connectSendClientCertificate handle = do
certRequested <- fmap scCertRequested get
when certRequested $ do
clientCert <- fmap (cpCertificate . scParams) get
sendPacket handle $ Handshake (Certificates $ maybe [] (:[]) clientCert)
connectSendClientKeyXchg :: Handle -> [Word8] -> TLSClient IO ()
connectSendClientKeyXchg handle prerand = do
ver <- fmap (cpConnectVersion . scParams) get
sendPacket handle $ Handshake (ClientKeyXchg ver prerand)
connectSendFinish :: Handle -> TLSClient IO ()
connectSendFinish handle = do
cf <- getHandshakeDigest True
sendPacket handle (Handshake $ Finished $ L.unpack cf)
{- | connect through a handle as a new TLS connection. -}
connect :: Handle -> ClientRandom -> [Word8] -> TLSClient IO ()
connect handle crand premasterRandom = do
connectSendClientHello handle crand
recvServerHello handle
connectSendClientCertificate handle
connectSendClientKeyXchg handle premasterRandom
{- maybe send certificateVerify -}
{- FIXME not implemented yet -}
sendPacket handle (ChangeCipherSpec)
lift $ hFlush handle
{- send Finished -}
connectSendFinish handle
{- receive changeCipherSpec -}
pktCCS <- recvPacket handle
case pktCCS of
Right ChangeCipherSpec -> return ()
x -> error ("unexpected reply. expecting change cipher spec " ++ show x)
{- receive Finished -}
pktFin <- recvPacket handle
case pktFin of
Right (Handshake (Finished _)) -> return ()
x -> error ("unexpected reply. expecting finished " ++ show x)
return ()
{- | sendData sends a bunch of data -}
sendData :: Handle -> L.ByteString -> TLSClient IO ()
sendData handle d = do
if L.length d > 16384
then do
let (sending, remain) = L.splitAt 16384 d
sendPacket handle $ AppData sending
sendData handle remain
else
sendPacket handle $ AppData d
{- | recvData get data out of Data packet, and automatically try to renegociate if
- a Handshake HelloRequest is received -}
recvData :: Handle -> TLSClient IO L.ByteString
recvData handle = do
pkt <- recvPacket handle
case pkt of
Right (AppData x) -> return x
Right (Handshake HelloRequest) -> do
-- SECURITY FIXME audit the rng here..
st <- getTLSState
let (bytes, rng') = getRandomBytes (stRandomGen st) 32
let (premaster, rng'') = getRandomBytes rng' 46
putTLSState $ st { stRandomGen = rng'' }
let crand = fromJust $ clientRandom bytes
connect handle crand premaster
recvData handle
Left err -> error ("error received: " ++ show err)
_ -> error "unexpected item"
{- | close a TLS connection.
- note that it doesn't close the handle, but just signal we're going to close
- the connection to the other side -}
close :: Handle -> TLSClient IO ()
close handle = do
sendPacket handle $ Alert (AlertLevel_Warning, CloseNotify)

View file

@ -0,0 +1,18 @@
-- |
-- Module : Network.TLS.Compression
-- License : BSD-style
-- Maintainer : Vincent Hanquez <vincent@snarc.org>
-- Stability : experimental
-- Portability : unknown
--
module Network.TLS.Compression
( Compression(..)
) where
import Data.Word
import Data.ByteString (ByteString)
data Compression = Compression
{ compressionID :: Word8
, compressionFct :: (ByteString -> ByteString)
}

101
Network/TLS/Crypto.hs Normal file
View file

@ -0,0 +1,101 @@
module Network.TLS.Crypto
( HashType(..)
, HashCtx
-- * incremental interface with algorithm type wrapping for genericity
, initHash
, updateHash
, finalizeHash
-- * single pass lazy bytestring interface for each algorithm
, hashMD5
, hashSHA1
-- * incremental interface for each algorithm
, initMD5
, updateMD5
, finalizeMD5
, initSHA1
, updateSHA1
, finalizeSHA1
-- * RSA stuff
, PublicKey(..)
, PrivateKey(..)
, rsaEncrypt
, rsaDecrypt
) where
import qualified Data.CryptoHash.SHA1 as SHA1
import qualified Data.CryptoHash.MD5 as MD5
import qualified Data.ByteString as B
import Data.ByteString.Lazy (ByteString)
import Codec.Crypto.RSA (PublicKey(..), PrivateKey(..))
import qualified Codec.Crypto.RSA as RSA
import Control.Spoon
import Random
data HashCtx =
SHA1 !SHA1.Ctx
| MD5 !MD5.Ctx
instance Show HashCtx where
show (SHA1 _) = "sha1"
show (MD5 _) = "md5"
data HashType = HashTypeSHA1 | HashTypeMD5
{- MD5 -}
initMD5 :: MD5.Ctx
initMD5 = MD5.init
updateMD5 :: MD5.Ctx -> B.ByteString -> MD5.Ctx
updateMD5 = MD5.update
finalizeMD5 :: MD5.Ctx -> B.ByteString
finalizeMD5 = MD5.finalize
hashMD5 :: ByteString -> B.ByteString
hashMD5 = MD5.hashlazy
{- SHA1 -}
initSHA1 :: SHA1.Ctx
initSHA1 = SHA1.init
updateSHA1 :: SHA1.Ctx -> B.ByteString -> SHA1.Ctx
updateSHA1 = SHA1.update
finalizeSHA1 :: SHA1.Ctx -> B.ByteString
finalizeSHA1 = SHA1.finalize
hashSHA1 :: ByteString -> B.ByteString
hashSHA1 = SHA1.hashlazy
{- generic Hashing -}
initHash :: HashType -> HashCtx
initHash HashTypeSHA1 = SHA1 (initSHA1)
initHash HashTypeMD5 = MD5 (initMD5)
updateHash :: HashCtx -> B.ByteString -> HashCtx
updateHash (SHA1 ctx) = SHA1 . updateSHA1 ctx
updateHash (MD5 ctx) = MD5 . updateMD5 ctx
finalizeHash :: HashCtx -> B.ByteString
finalizeHash (SHA1 ctx) = finalizeSHA1 ctx
finalizeHash (MD5 ctx) = finalizeMD5 ctx
{- RSA reexport and maybification -}
{- on using spoon:
because we use rsa Encrypt/Decrypt in a pure context, catching the exception
when the key is not correctly set or the data isn't correct.
need to fix the RSA package to return "Either String X".
-}
rsaEncrypt :: RandomGen g => g -> PublicKey -> ByteString -> Maybe (ByteString, g)
rsaEncrypt g pk b = teaspoon (RSA.rsaes_pkcs1_v1_5_encrypt g pk b)
rsaDecrypt :: PrivateKey -> ByteString -> Maybe ByteString
rsaDecrypt pk b = teaspoon (RSA.rsaes_pkcs1_v1_5_decrypt pk b)

63
Network/TLS/MAC.hs Normal file
View file

@ -0,0 +1,63 @@
module Network.TLS.MAC
( hmacMD5
, hmacSHA1
, hmacSHA256
, prf_MD5
, prf_SHA1
, prf_MD5SHA1
) where
import qualified Data.CryptoHash.MD5 as MD5
import qualified Data.CryptoHash.SHA1 as SHA1
import qualified Data.CryptoHash.SHA256 as SHA256
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString as B
import Data.ByteString.Lazy (ByteString)
import Data.Bits (xor)
lazyOfStrict :: B.ByteString -> ByteString
lazyOfStrict b = L.fromChunks [ b ]
hmac :: (ByteString -> ByteString) -> Int -> ByteString -> ByteString -> ByteString
hmac f bl secret msg =
f $! L.append opad (f $! L.append ipad msg)
where
opad = L.map (xor 0x5c) k'
ipad = L.map (xor 0x36) k'
k' = L.append kt pad
where
kt = if L.length secret > fromIntegral bl then f secret else secret
pad = L.replicate (fromIntegral bl - L.length kt) 0
hmacMD5 :: ByteString -> ByteString -> ByteString
hmacMD5 secret msg = hmac (lazyOfStrict . MD5.hashlazy) 64 secret msg
hmacSHA1 :: ByteString -> ByteString -> ByteString
hmacSHA1 secret msg = hmac (lazyOfStrict . SHA1.hashlazy) 64 secret msg
hmacSHA256 :: ByteString -> ByteString -> ByteString
hmacSHA256 secret msg = hmac (lazyOfStrict . SHA256.hashlazy) 64 secret msg
hmacIter :: (ByteString -> ByteString -> ByteString) -> ByteString -> ByteString -> ByteString -> Int -> [ByteString]
hmacIter f secret seed aprev len =
let an = f secret aprev in
let out = f secret (L.concat [an, seed]) in
let digestsize = fromIntegral $ L.length out in
if digestsize >= len
then [ L.take (fromIntegral len) out ]
else out : hmacIter f secret seed an (len - digestsize)
prf_SHA1 :: ByteString -> ByteString -> Int -> ByteString
prf_SHA1 secret seed len = L.concat $ hmacIter hmacSHA1 secret seed seed len
prf_MD5 :: ByteString -> ByteString -> Int -> ByteString
prf_MD5 secret seed len = L.concat $ hmacIter hmacMD5 secret seed seed len
prf_MD5SHA1 :: ByteString -> ByteString -> Int -> ByteString
prf_MD5SHA1 secret seed len =
L.pack $ L.zipWith xor (prf_MD5 s1 seed len) (prf_SHA1 s2 seed len)
where
slen = L.length secret
s1 = L.take (slen `div` 2 + slen `mod` 2) secret
s2 = L.drop (slen `div` 2) secret

408
Network/TLS/Packet.hs Normal file
View file

@ -0,0 +1,408 @@
-- |
-- Module : Network.TLS.Packet
-- License : BSD-style
-- Maintainer : Vincent Hanquez <vincent@snarc.org>
-- Stability : experimental
-- Portability : unknown
--
-- the Packet module contains everything necessary to serialize and deserialize things
-- with only explicit parameters, no TLS state is involved here.
--
module Network.TLS.Packet
(
-- * marshall functions for header messages
decodeHeader
, encodeHeader
-- * marshall functions for alert messages
, decodeAlert
, encodeAlert
-- * marshall functions for handshake messages
, decodeHandshakeHeader
, decodeHandshake
, encodeHandshake
, encodeHandshakeHeader
, encodeHandshakeContent
-- * marshall functions for change cipher spec message
, decodeChangeCipherSpec
, encodeChangeCipherSpec
-- * generate things for packet content
, generateMasterSecret
, generateKeyBlock
, generateClientFinished
, generateServerFinished
) where
import Data.Word
import Network.TLS.Wire
import Data.Either (partitionEithers)
import Data.Maybe (fromJust, isNothing)
import Control.Monad
import Control.Monad.Error
import Network.TLS.Struct
import Data.Certificate.X509
import Network.TLS.Crypto
import Network.TLS.MAC
import Data.ByteString.Lazy (ByteString)
import qualified Data.ByteString.Lazy as L (pack, length, concat, fromChunks)
import qualified Data.ByteString as B
{-
- decode and encode headers
-}
decodeHeader :: ByteString -> Either TLSError Header
decodeHeader = runGet $ do
ty <- getWord8
major <- getWord8
minor <- getWord8
len <- getWord16
case (valToType ty, verOfNum (major, minor)) of
(Just y, Just v) -> return $ Header y v len
(Nothing, _) -> throwError (Error_Packet "invalid type")
(_, Nothing) -> throwError (Error_Packet "invalid version")
encodeHeader :: Header -> ByteString
encodeHeader (Header pt ver len) =
{- FIXME check len <= 2^14 -}
runPut (putWord8 (valOfType pt) >> putWord8 major >> putWord8 minor >> putWord16 len)
where (major, minor) = numericalVer ver
{-
- decode and encode ALERT
-}
decodeAlert :: ByteString -> Either TLSError (AlertLevel, AlertDescription)
decodeAlert = runGet $ do
al <- getWord8
ad <- getWord8
case (valToType al, valToType ad) of
(Just a, Just d) -> return (a, d)
(Nothing, _) -> throwError (Error_Packet "missing alert level")
(_, Nothing) -> throwError (Error_Packet "missing alert description")
encodeAlert :: (AlertLevel, AlertDescription) -> ByteString
encodeAlert (al, ad) = runPut (putWord8 (valOfType al) >> putWord8 (valOfType ad))
{- decode and encode HANDSHAKE -}
decodeHandshakeHeader :: ByteString -> Either TLSError (HandshakeType, ByteString)
decodeHandshakeHeader = runGet $ do
tyopt <- getWord8 >>= return . valToType
ty <- if isNothing tyopt
then throwError (Error_Unknown_Type "handshake type")
else return $ fromJust tyopt
len <- getWord24
content <- getBytes len
empty <- isEmpty
unless empty (throwError (Error_Internal_Packet_Remaining 1))
return (ty, L.fromChunks [content])
decodeHandshake :: Version -> HandshakeType -> ByteString -> Either TLSError Handshake
decodeHandshake ver ty = runGet $ case ty of
HandshakeType_HelloRequest -> decodeHelloRequest
HandshakeType_ClientHello -> decodeClientHello
HandshakeType_ServerHello -> decodeServerHello
HandshakeType_Certificate -> decodeCertificates
HandshakeType_ServerKeyXchg -> decodeServerKeyXchg ver
HandshakeType_CertRequest -> decodeCertRequest ver
HandshakeType_ServerHelloDone -> decodeServerHelloDone
HandshakeType_CertVerify -> decodeCertVerify
HandshakeType_ClientKeyXchg -> decodeClientKeyXchg
HandshakeType_Finished -> decodeFinished ver
decodeHelloRequest :: Get Handshake
decodeHelloRequest = return HelloRequest
decodeClientHello :: Get Handshake
decodeClientHello = do
ver <- getVersion
random <- getClientRandom32
session <- getSession
ciphers <- getWords16
compressions <- getWords8
r <- remaining
exts <- if ver >= TLS12 && r > 0
then fmap fromIntegral getWord16 >>= getExtensions >>= return . Just
else return Nothing
return $ ClientHello ver random session ciphers compressions exts
decodeServerHello :: Get Handshake
decodeServerHello = do
ver <- getVersion
random <- getServerRandom32
session <- getSession
cipherid <- getWord16
compressionid <- getWord8
r <- remaining
exts <- if ver >= TLS12 && r > 0
then fmap fromIntegral getWord16 >>= getExtensions >>= return . Just
else return Nothing
return $ ServerHello ver random session cipherid compressionid exts
decodeServerHelloDone :: Get Handshake
decodeServerHelloDone = return ServerHelloDone
decodeCertificates :: Get Handshake
decodeCertificates = do
certslen <- getWord24
certs <- getCerts certslen >>= return . map (decodeCertificate . L.fromChunks . (:[]))
let (l, r) = partitionEithers certs
if length l > 0
then throwError $ Error_Certificate $ show l
else return $ Certificates r
decodeFinished :: Version -> Get Handshake
decodeFinished ver = do
-- unfortunately passing the verify_data_size here would be tedious for >=TLS12,
-- so just return the remaining string.
len <- if ver >= TLS12
then remaining
else return 12
opaque <- getBytes (fromIntegral len)
return $ Finished $ B.unpack opaque
getSignatureHashAlgorithm :: Int -> Get [ (HashAlgorithm, SignatureAlgorithm) ]
getSignatureHashAlgorithm 0 = return []
getSignatureHashAlgorithm len = do
h <- fmap (fromJust . valToType) getWord8
s <- fmap (fromJust . valToType) getWord8
xs <- getSignatureHashAlgorithm (len - 2)
return ((h, s) : xs)
decodeCertRequest :: Version -> Get Handshake
decodeCertRequest ver = do
certTypes <- fmap (map (fromJust . valToType . fromIntegral)) getWords8
sigHashAlgs <- if ver >= TLS12
then do
sighashlen <- getWord16
fmap Just $ getSignatureHashAlgorithm $ fromIntegral sighashlen
else return Nothing
dNameLen <- getWord16
when (ver < TLS12 && dNameLen < 3) $ throwError (Error_Misc "certrequest distinguishname not of the correct size")
dName <- getBytes $ fromIntegral dNameLen
return $ CertRequest certTypes sigHashAlgs (B.unpack dName)
decodeCertVerify :: Get Handshake
decodeCertVerify =
{- FIXME -}
return $ CertVerify []
decodeClientKeyXchg :: Get Handshake
decodeClientKeyXchg = do
ver <- getVersion
ran <- getRandom46
return $ ClientKeyXchg ver ran
-- FIXME need to work out how we marshall an opaque number
--numberise :: ByteString -> Integer
numberise _ = 0
decodeServerKeyXchg_DH :: Get ServerDHParams
decodeServerKeyXchg_DH = do
p <- getWord16 >>= getBytes . fromIntegral
g <- getWord16 >>= getBytes . fromIntegral
y <- getWord16 >>= getBytes . fromIntegral
return $ ServerDHParams { dh_p = numberise p, dh_g = numberise g, dh_Ys = numberise y }
decodeServerKeyXchg_RSA :: Get ServerRSAParams
decodeServerKeyXchg_RSA = do
modulus <- getWord16 >>= getBytes . fromIntegral
expo <- getWord16 >>= getBytes . fromIntegral
return $ ServerRSAParams { rsa_modulus = numberise modulus, rsa_exponent = numberise expo }
decodeServerKeyXchg :: Version -> Get Handshake
decodeServerKeyXchg ver = do
-- mostly unimplemented
skxAlg <- case ver of
TLS12 -> return $ SKX_RSA Nothing
TLS10 -> do
rsaparams <- decodeServerKeyXchg_RSA
return $ SKX_RSA $ Just rsaparams
_ -> do
return $ SKX_RSA Nothing
return (ServerKeyXchg skxAlg)
encodeHandshake :: Handshake -> ByteString
encodeHandshake o =
let content = runPut $ encodeHandshakeContent o in
let len = fromIntegral $ L.length content in
let header = runPut $ encodeHandshakeHeader (typeOfHandshake o) len in
L.concat [ header, content ]
encodeHandshakeHeader :: HandshakeType -> Int -> Put
encodeHandshakeHeader ty len = putWord8 (valOfType ty) >> putWord24 len
encodeHandshakeContent :: Handshake -> Put
encodeHandshakeContent (ClientHello version random session cipherIDs compressionIDs exts) = do
putVersion version
putClientRandom32 random
putSession session
putWords16 cipherIDs
putWords8 compressionIDs
putExtensions exts
return ()
encodeHandshakeContent (ServerHello version random session cipherID compressionID exts) =
putVersion version >> putServerRandom32 random >> putSession session
>> putWord16 cipherID >> putWord8 compressionID
>> putExtensions exts >> return ()
encodeHandshakeContent (Certificates certs) =
putWord24 len >> putLazyByteString certbs
where
certbs = runPut $ mapM_ putCert certs
len = fromIntegral $ L.length certbs
encodeHandshakeContent (ClientKeyXchg version random) = do
putVersion version
putRandom46 random
encodeHandshakeContent (ServerKeyXchg _) = do
-- FIXME
return ()
encodeHandshakeContent (HelloRequest) = return ()
encodeHandshakeContent (ServerHelloDone) = return ()
encodeHandshakeContent (CertRequest certTypes sigAlgs certAuthorities) = do
putWords8 (map valOfType certTypes)
case sigAlgs of
Nothing -> return ()
Just l -> putWords16 $ map (\(x,y) -> (fromIntegral $ valOfType x) * 256 + (fromIntegral $ valOfType y)) l
putByteString $ B.pack certAuthorities
encodeHandshakeContent (CertVerify _) = undefined
encodeHandshakeContent (Finished opaque) = mapM_ putWord8 opaque
{- marshall helpers -}
getVersion :: Get Version
getVersion = do
major <- getWord8
minor <- getWord8
case verOfNum (major, minor) of
Just v -> return v
Nothing -> throwError (Error_Unknown_Version major minor)
putVersion :: Version -> Put
putVersion ver = putWord8 major >> putWord8 minor
where (major, minor) = numericalVer ver
{- FIXME make sure it return error if not 32 available -}
getRandom32 :: Get [Word8]
getRandom32 = fmap B.unpack $ getBytes 32
getServerRandom32 :: Get ServerRandom
getServerRandom32 = fmap ServerRandom getRandom32
getClientRandom32 :: Get ClientRandom
getClientRandom32 = fmap ClientRandom getRandom32
putRandom32 :: [Word8] -> Put
putRandom32 = mapM_ putWord8
putClientRandom32 :: ClientRandom -> Put
putClientRandom32 (ClientRandom r) = putRandom32 r
putServerRandom32 :: ServerRandom -> Put
putServerRandom32 (ServerRandom r) = putRandom32 r
getRandom46 :: Get [Word8]
getRandom46 = fmap B.unpack $ getBytes 46
putRandom46 :: [Word8] -> Put
putRandom46 = mapM_ putWord8
getSession :: Get Session
getSession = do
len8 <- getWord8
case fromIntegral len8 of
0 -> return $ Session Nothing
len -> fmap (Session . Just . B.unpack) $ getBytes len
putSession :: Session -> Put
putSession (Session session) =
case session of
Nothing -> putWord8 0
Just s -> putWord8 (fromIntegral $ length s) >> mapM_ putWord8 s
getCerts :: Int -> Get [B.ByteString]
getCerts 0 = return []
getCerts len = do
certlen <- getWord24
cert <- getBytes certlen
certxs <- getCerts (len - certlen - 3)
return (cert : certxs)
putCert :: Certificate -> Put
putCert cert = putWord24 (fromIntegral $ L.length content) >> putLazyByteString content
where content = encodeCertificate cert
getExtensions :: Int -> Get [Extension]
getExtensions 0 = return []
getExtensions len = do
extty <- getWord16
extdatalen <- getWord16
extdata <- getBytes $ fromIntegral extdatalen
extxs <- getExtensions (len - fromIntegral extdatalen - 4)
return $ (extty, B.unpack extdata) : extxs
putExtension :: Extension -> Put
putExtension (ty, l) = do
putWord16 ty
putWord16 (fromIntegral $ length l)
putByteString (B.pack l)
putExtensions :: Maybe [Extension] -> Put
putExtensions Nothing = return ()
putExtensions (Just es) =
putWord16 (fromIntegral $ L.length extbs) >> putLazyByteString extbs
where
extbs = runPut $ mapM_ putExtension es
{-
- decode and encode ALERT
-}
decodeChangeCipherSpec :: ByteString -> Either TLSError ()
decodeChangeCipherSpec b = do
x <- runGet getWord8 b
if x == 1 then Right () else Left $ Error_Misc "unknown change cipher spec content"
encodeChangeCipherSpec :: ByteString
encodeChangeCipherSpec = runPut (putWord8 1)
{-
- generate things for packet content
-}
generateMasterSecret :: ByteString -> ClientRandom -> ServerRandom -> ByteString
generateMasterSecret premasterSecret (ClientRandom c) (ServerRandom s) =
prf_MD5SHA1 premasterSecret seed 48
where
label = map (toEnum . fromEnum) "master secret"
seed = L.concat $ map L.pack [ label, c, s]
generateKeyBlock :: ClientRandom -> ServerRandom -> ByteString -> Int -> ByteString
generateKeyBlock (ClientRandom c) (ServerRandom s) mastersecret kbsize =
prf_MD5SHA1 mastersecret seed kbsize
where
label = map (toEnum . fromEnum) "key expansion"
seed = L.concat $ map L.pack [ label, s, c ]
generateFinished :: String -> ByteString -> HashCtx -> HashCtx -> ByteString
generateFinished label mastersecret md5ctx sha1ctx =
prf_MD5SHA1 mastersecret seed 12
where
plabel = B.pack $ map (toEnum . fromEnum) label
seed = L.fromChunks [ plabel, finalizeHash md5ctx, finalizeHash sha1ctx ]
generateClientFinished :: ByteString -> HashCtx -> HashCtx -> ByteString
generateClientFinished = generateFinished "client finished"
generateServerFinished :: ByteString -> HashCtx -> HashCtx -> ByteString
generateServerFinished = generateFinished "server finished"

194
Network/TLS/Receiving.hs Normal file
View file

@ -0,0 +1,194 @@
{-# LANGUAGE GeneralizedNewtypeDeriving, FlexibleContexts #-}
-- |
-- Module : Network.TLS.Receiving
-- License : BSD-style
-- Maintainer : Vincent Hanquez <vincent@snarc.org>
-- Stability : experimental
-- Portability : unknown
--
-- the Receiving module contains calls related to unmarshalling packets according
-- to the TLS state
--
module Network.TLS.Receiving (
readPacket
) where
import Control.Monad.State
import Control.Monad.Error
import Data.Maybe
import Data.ByteString.Lazy (ByteString)
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString as B
import Network.TLS.Struct
import Network.TLS.Packet
import Network.TLS.State
import Network.TLS.Cipher
import Network.TLS.Crypto
import Network.TLS.SRandom
import Data.Certificate.X509
newtype TLSRead a = TLSR { runTLSR :: ErrorT TLSError (State TLSState) a }
deriving (Monad, MonadError TLSError)
instance Functor TLSRead where
fmap f = TLSR . fmap f . runTLSR
instance MonadTLSState TLSRead where
putTLSState x = TLSR (lift $ put x)
getTLSState = TLSR (lift get)
runTLSRead :: MonadTLSState m => TLSRead a -> m (Either TLSError a)
runTLSRead f = do
st <- getTLSState
let (a, newst) = runState (runErrorT (runTLSR f)) st
putTLSState newst
return a
returnEither :: Either TLSError a -> TLSRead a
returnEither (Left err) = throwError err
returnEither (Right a) = return a
readPacket :: MonadTLSState m => Header -> EncryptedData -> m (Either TLSError Packet)
readPacket hdr@(Header ProtocolType_AppData _ _) content =
runTLSRead (fmap AppData $ decryptContent hdr content)
readPacket hdr@(Header ProtocolType_Alert _ _) content =
runTLSRead (decryptContent hdr content >>= returnEither . decodeAlert >>= return . Alert)
readPacket hdr@(Header ProtocolType_ChangeCipherSpec _ _) content = runTLSRead $ do
dcontent <- decryptContent hdr content
returnEither $ decodeChangeCipherSpec dcontent
switchRxEncryption
isClientContext >>= \cc -> when (not cc) setKeyBlock
return ChangeCipherSpec
readPacket hdr@(Header ProtocolType_Handshake ver _) content =
runTLSRead (decryptContent hdr content >>= processHsPacket ver)
decryptRSA :: MonadTLSState m => ByteString -> m (Maybe ByteString)
decryptRSA econtent = do
rsapriv <- getTLSState >>= return . fromJust . hstRSAPrivateKey . fromJust . stHandshake
return $ rsaDecrypt rsapriv (L.drop 2 econtent)
setMasterSecretRandom :: ByteString -> TLSRead ()
setMasterSecretRandom content = do
st <- getTLSState
let (bytes, g') = getRandomBytes (stRandomGen st) (fromIntegral $ L.length content)
putTLSState $ st { stRandomGen = g' }
setMasterSecret (L.pack bytes)
processClientKeyXchg :: Version -> ByteString -> TLSRead ()
processClientKeyXchg ver content = do
{- the TLS protocol expect the initial client version received in the ClientHello, not the negociated version -}
expectedVer <- getTLSState >>= return . hstClientVersion . fromJust . stHandshake
if expectedVer /= ver
then setMasterSecretRandom content
else setMasterSecret content
processClientFinished :: FinishedData -> TLSRead ()
processClientFinished fdata = do
cc <- getTLSState >>= return . stClientContext
expected <- getHandshakeDigest (not cc)
when (expected /= L.pack fdata) $ do
-- FIXME don't fail, but report the error so that the code can send a BadMac Alert.
fail ("client mac failure: expecting " ++ show expected ++ " received " ++ (show $L.pack fdata))
return ()
processHsPacket :: Version -> ByteString -> TLSRead Packet
processHsPacket ver dcontent = do
(ty, econtent) <- returnEither $ decodeHandshakeHeader dcontent
-- SECURITY FIXME if RSA fail, we need to generate a random master secret and not fail.
content <- case ty of
HandshakeType_ClientKeyXchg -> do
copt <- decryptRSA econtent
return $ maybe econtent id copt
_ ->
return econtent
hs <- case (ty, decodeHandshake ver ty content) of
(_, Right x) -> return x
(HandshakeType_ClientKeyXchg, Left _) -> return $ ClientKeyXchg SSL2 []
(_, Left err) -> throwError err
clientmode <- isClientContext
case hs of
ClientHello cver ran _ _ _ _ -> unless clientmode $ do
startHandshakeClient cver ran
ServerHello sver ran _ _ _ _ -> when clientmode $ do
setServerRandom ran
setVersion sver
Certificates [cert] -> when clientmode $ do processCertificate cert
ClientKeyXchg cver _ -> unless clientmode $ do
processClientKeyXchg cver content
Finished fdata -> processClientFinished fdata
_ -> return ()
when (finishHandshakeTypeMaterial ty) (updateHandshakeDigest dcontent)
return $ Handshake hs
decryptContentReally :: Header -> EncryptedData -> TLSRead ByteString
decryptContentReally hdr e = do
st <- getTLSState
unencrypted_content <- decryptData e
let digestSize = cipherDigestSize $ fromJust $ stCipher st
let (unencrypted_msg, digest) = L.splitAt (L.length unencrypted_content - fromIntegral digestSize) unencrypted_content
let (Header pt ver _) = hdr
let new_hdr = Header pt ver (fromIntegral $ L.length unencrypted_msg)
expected_digest <- makeDigest False new_hdr unencrypted_msg
if expected_digest == digest
then return $ unencrypted_msg
else throwError $ Error_Digest (L.unpack expected_digest, L.unpack digest)
decryptContent :: Header -> EncryptedData -> TLSRead ByteString
decryptContent hdr e@(EncryptedData b) = do
st <- getTLSState
if stRxEncrypted st
then decryptContentReally hdr e
else return b
takelast :: Int -> [a] -> [a]
takelast i b = drop (length b - i) b
decryptData :: EncryptedData -> TLSRead ByteString
decryptData (EncryptedData econtent) = do
st <- getTLSState
assert "decrypt data"
[ ("cipher", isNothing $ stCipher st)
, ("crypt state", isNothing $ stRxCryptState st) ]
let cipher = fromJust $ stCipher st
let cst = fromJust $ stRxCryptState st
let padding_size = fromIntegral $ cipherPaddingSize cipher
let writekey = B.pack $ cstKey cst
let iv = B.pack $ cstIV cst
contentpadded <- case cipherF cipher of
CipherNoneF -> fail "none decrypt"
CipherBlockF _ decryptF -> do
{- update IV -}
let newiv = takelast padding_size $ L.unpack econtent
putTLSState $ st { stRxCryptState = Just $ cst { cstIV = newiv } }
return $ decryptF writekey iv econtent
CipherStreamF initF _ decryptF -> do
let (content, newiv) = decryptF (if iv /= B.empty then iv else initF writekey) econtent
{- update Ctx -}
putTLSState $ st { stRxCryptState = Just $ cst { cstIV = B.unpack newiv } }
return $ content
let content =
if cipherPaddingSize cipher > 0
then
let pb = L.last contentpadded + 1 in
fst $ L.splitAt ((L.length contentpadded) - fromIntegral pb) contentpadded
else contentpadded
return content
processCertificate :: Certificate -> TLSRead ()
processCertificate cert = do
case certPubKey cert of
PubKey _ (PubKeyRSA (lm, m, e)) -> do
let pk = PublicKey { public_size = fromIntegral lm, public_n = m, public_e = e }
setPublicKey pk
_ -> return ()

30
Network/TLS/SRandom.hs Normal file
View file

@ -0,0 +1,30 @@
-- this is probably not a very good random interface, nor it has any good randomness capability.
-- the module is just here until a really good CPRNG implementation come up..
module Network.TLS.SRandom
( SRandomGen
, makeSRandomGen
, getRandomByte
, getRandomBytes
) where
import Random
import Control.Arrow (first)
import Data.Word
type SRandomGen = StdGen
makeSRandomGen :: Int -> SRandomGen
makeSRandomGen i = mkStdGen i
getRandomByte :: SRandomGen -> (Word8, SRandomGen)
getRandomByte rng = first fromIntegral $ next rng
getRandomBytes :: SRandomGen -> Int -> ([Word8], SRandomGen)
getRandomBytes rng n =
let list = helper rng n in
(map fst list, snd $ last list)
where
helper _ 0 = []
helper g i =
let (b, g') = getRandomByte g in
(b, g') : helper g' (i-1)

178
Network/TLS/Sending.hs Normal file
View file

@ -0,0 +1,178 @@
{-# LANGUAGE FlexibleContexts #-}
-- |
-- Module : Network.TLS.Sending
-- License : BSD-style
-- Maintainer : Vincent Hanquez <vincent@snarc.org>
-- Stability : experimental
-- Portability : unknown
--
-- the Sending module contains calls related to marshalling packets according
-- to the TLS state
--
module Network.TLS.Sending (
writePacket
) where
import Control.Monad.State
import Data.Binary.Put (runPut, putWord16be)
import Data.Maybe
import Data.ByteString.Lazy (ByteString)
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString as B
import Network.TLS.Struct
import Network.TLS.Packet
import Network.TLS.State
import Network.TLS.Cipher
import Network.TLS.Crypto
{-
- 'makePacketData' create a Header and a content bytestring related to a packet
- this doesn't change any state
-}
makePacketData :: MonadTLSState m => Packet -> m (Header, ByteString)
makePacketData pkt = do
ver <- getTLSState >>= return . stVersion
content <- writePacketContent pkt
let hdr = Header (packetType pkt) ver (fromIntegral $ L.length content)
return (hdr, content)
{-
- Handshake data need to update a digest
-}
processPacketData :: MonadTLSState m => (Header, ByteString) -> m (Header, ByteString)
processPacketData dat@(Header ty _ _, content) = do
when (ty == ProtocolType_Handshake) (updateHandshakeDigest content)
return dat
{-
- when Tx Encrypted is set, we pass the data through encryptContent, otherwise
- we just return the packet
-}
encryptPacketData :: MonadTLSState m => (Header, ByteString) -> m (Header, ByteString)
encryptPacketData dat = do
st <- getTLSState
if stTxEncrypted st
then encryptContent dat
else return dat
{-
- 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 :: MonadTLSState m => (Header, ByteString) -> m (Header, ByteString)
postprocessPacketData dat@(Header ProtocolType_ChangeCipherSpec _ _, _) =
switchTxEncryption >> isClientContext >>= \cc -> when cc setKeyBlock >> return dat
postprocessPacketData dat = return dat
{-
- marshall packet data
-}
encodePacket :: MonadTLSState m => (Header, ByteString) -> m ByteString
encodePacket (hdr, content) = return $ L.concat [ encodeHeader hdr, content ]
{-
- writePacket transform a packet into marshalled data related to current state
- and updating state on the go
-}
writePacket :: MonadTLSState m => Packet -> m ByteString
writePacket pkt = makePacketData pkt >>= processPacketData >>=
encryptPacketData >>= postprocessPacketData >>= encodePacket
{------------------------------------------------------------------------------}
{- SENDING Helpers -}
{------------------------------------------------------------------------------}
{- if the RSA encryption fails we just return an empty bytestring, and let the protocol
- fail by itself; however it would be probably better to just report it since it's an internal problem.
-}
encryptRSA :: MonadTLSState m => ByteString -> m ByteString
encryptRSA content = do
st <- getTLSState
let g = stRandomGen st
let rsakey = fromJust $ hstRSAPublicKey $ fromJust $ stHandshake st
case rsaEncrypt g rsakey content of
Nothing -> return L.empty
Just (econtent, g') -> do
putTLSState (st { stRandomGen = g' })
return econtent
encryptContent :: MonadTLSState m => (Header, ByteString) -> m (Header, ByteString)
encryptContent (hdr@(Header pt ver _), content) = do
digest <- makeDigest True hdr content
encrypted_msg <- encryptData $ L.concat [content, digest]
let hdrnew = Header pt ver (fromIntegral $ L.length encrypted_msg)
return (hdrnew, encrypted_msg)
takelast :: Int -> [a] -> [a]
takelast i b = drop (length b - i) b
encryptData :: MonadTLSState m => ByteString -> m ByteString
encryptData content = do
st <- getTLSState
assert "encrypt data"
[ ("cipher", isNothing $ stCipher st)
, ("crypt state", isNothing $ stTxCryptState st) ]
let cipher = fromJust $ stCipher st
let cst = fromJust $ stTxCryptState st
let padding_size = fromIntegral $ cipherPaddingSize cipher
let msg_len = L.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
L.replicate padbyte' (fromIntegral (padbyte' - 1))
else
L.empty
let writekey = B.pack $ cstKey cst
let iv = B.pack $ cstIV cst
econtent <- case cipherF cipher of
CipherNoneF -> fail "none encrypt"
CipherBlockF encrypt _ -> do
let e = encrypt writekey iv (L.concat [ content, padding ])
let newiv = takelast (fromIntegral padding_size) $ L.unpack e
putTLSState $ st { stTxCryptState = Just $ cst { cstIV = newiv } }
return e
CipherStreamF initF encryptF _ -> do
let (e, newiv) = encryptF (if iv /= B.empty then iv else initF writekey) content
putTLSState $ st { stTxCryptState = Just $ cst { cstIV = B.unpack newiv } }
return e
return econtent
encodePacketContent :: Packet -> ByteString
encodePacketContent (Handshake h) = encodeHandshake h
encodePacketContent (Alert a) = encodeAlert a
encodePacketContent (ChangeCipherSpec) = encodeChangeCipherSpec
encodePacketContent (AppData x) = x
writePacketContent :: MonadTLSState m => Packet -> m ByteString
writePacketContent (Handshake ckx@(ClientKeyXchg _ _)) = do
let premastersecret = runPut $ encodeHandshakeContent ckx
setMasterSecret premastersecret
econtent <- encryptRSA premastersecret
let extralength = runPut $ putWord16be $ fromIntegral $ L.length econtent
let hdr = runPut $ encodeHandshakeHeader (typeOfHandshake ckx) (fromIntegral (L.length econtent + 2))
return $ L.concat [hdr, extralength, econtent]
writePacketContent pkt@(Handshake (ClientHello ver crand _ _ _ _)) = do
cc <- isClientContext
when cc (startHandshakeClient ver crand)
return $ encodePacketContent pkt
writePacketContent pkt@(Handshake (ServerHello ver srand _ _ _ _)) = do
cc <- isClientContext
unless cc $ do
setVersion ver
setServerRandom srand
return $ encodePacketContent pkt
writePacketContent pkt = return $ encodePacketContent pkt

241
Network/TLS/Server.hs Normal file
View file

@ -0,0 +1,241 @@
{-# LANGUAGE GeneralizedNewtypeDeriving, MultiParamTypeClasses #-}
-- |
-- Module : Network.TLS.Server
-- License : BSD-style
-- Maintainer : Vincent Hanquez <vincent@snarc.org>
-- Stability : experimental
-- Portability : unknown
--
-- the Server module contains the necessary calls to create a listening TLS socket
-- aka. a server socket.
--
module Network.TLS.Server
( TLSServerParams(..)
, TLSStateServer
, runTLSServer
-- * low level packet sending receiving.
, recvPacket
, sendPacket
-- * API, warning probably subject to change
, listen
, sendData
, recvData
, close
) where
import Data.Word
import Data.Maybe
import Data.List (intersect, find)
import Control.Monad.Trans
import Control.Monad.State
import Codec.Crypto.RSA (PrivateKey(..))
import Data.Certificate.X509
import qualified Data.Certificate.Key as CertificateKey
import Network.TLS.Cipher
import Network.TLS.Struct
import Network.TLS.Packet
import Network.TLS.State
import Network.TLS.Sending
import Network.TLS.Receiving
import Network.TLS.SRandom
import qualified Data.ByteString.Lazy as L
import System.IO (Handle, hFlush)
type TLSServerCert = (L.ByteString, Certificate, CertificateKey.PrivateKey)
data TLSServerParams = TLSServerParams
{ spAllowedVersions :: [Version] -- ^ allowed versions that we can use
, spSessions :: [[Word8]] -- ^ placeholder for futur known sessions
, spCiphers :: [Cipher] -- ^ all ciphers that the server side support
, spCertificate :: Maybe TLSServerCert -- ^ the certificate we serve to the client
, spWantClientCert :: Bool -- ^ configure if we do a cert request to the client
}
data TLSStateServer = TLSStateServer
{ scParams :: TLSServerParams -- ^ server params and config for this connection
, scTLSState :: TLSState -- ^ server TLS State for this connection
}
newtype TLSServer m a = TLSServer { runTLSC :: StateT TLSStateServer m a }
deriving (Monad, MonadState TLSStateServer)
instance Monad m => MonadTLSState (TLSServer m) where
getTLSState = TLSServer (get >>= return . scTLSState)
putTLSState s = TLSServer (get >>= put . (\st -> st { scTLSState = s }))
instance MonadTrans TLSServer where
lift = TLSServer . lift
instance Monad m => Functor (TLSServer m) where
fmap f = TLSServer . fmap f . runTLSC
runTLSServerST :: TLSServer m a -> TLSStateServer -> m (a, TLSStateServer)
runTLSServerST f s = runStateT (runTLSC f) s
runTLSServer :: TLSServer m a -> TLSServerParams -> SRandomGen -> m (a, TLSStateServer)
runTLSServer f params rng = runTLSServerST f (TLSStateServer { scParams = params, scTLSState = state })
where state = (newTLSState rng) { stVersion = TLS10, stClientContext = False }
{- | receive a single TLS packet or on error a TLSError -}
recvPacket :: Handle -> TLSServer IO (Either TLSError Packet)
recvPacket handle = do
hdr <- lift $ L.hGet handle 5 >>= return . decodeHeader
case hdr of
Left err -> return $ Left err
Right header@(Header _ _ readlen) -> do
content <- lift $ L.hGet handle (fromIntegral readlen)
readPacket header (EncryptedData content)
{- | send a single TLS packet -}
sendPacket :: Handle -> Packet -> TLSServer IO ()
sendPacket handle pkt = do
dataToSend <- writePacket pkt
lift $ L.hPut handle dataToSend
handleClientHello :: Handshake -> TLSServer IO ()
handleClientHello (ClientHello ver _ _ ciphers compressionID _) = do
cfg <- get >>= return . scParams
when (not $ elem ver (spAllowedVersions cfg)) $ do
{- unsupported version -}
fail "unsupported version"
let commonCiphers = intersect ciphers (map cipherID $ spCiphers cfg)
when (commonCiphers == []) $ do
{- unsupported cipher -}
fail ("unsupported cipher: " ++ show ciphers ++ " : server : " ++ (show $ map cipherID $ spCiphers cfg))
when (not $ elem 0 compressionID) $ do
{- unsupported compression -}
fail "unsupported compression"
modifyTLSState (\st -> st
{ stVersion = ver
, stCipher = find (\c -> cipherID c == (head commonCiphers)) (spCiphers cfg)
})
handleClientHello _ = do
fail "unexpected handshake type received. expecting client hello"
expectingPacket :: (Either TLSError Packet) -> ProtocolType -> TLSServer IO ()
expectingPacket pkt expectedType = do
apkt <- case pkt of
Right x -> return x
Left tlserror -> fail ("expecting packet but got error " ++ show tlserror)
when (packetType apkt /= expectedType) $ do
fail ("unexpected packet received, expecting " ++ show expectedType)
return ()
expectingHandshake :: (Either TLSError Packet) -> HandshakeType -> TLSServer IO ()
expectingHandshake pkt expectedType = do
hs <- case pkt of
Right (Handshake hs) -> return hs
Right _ -> fail ("unexpected packet received, expecting handshake " ++ show expectedType)
Left tlserror -> fail ("expecting handshake but got error " ++ show tlserror)
when (typeOfHandshake hs /= expectedType) $ do
fail ("unexpected handshake received, expecting " ++ show expectedType)
return ()
handshakeSendServerData :: Handle -> ServerRandom -> TLSServer IO ()
handshakeSendServerData handle srand = do
sp <- get >>= return . scParams
st <- getTLSState
let cipher = fromJust $ stCipher st
let srvhello = ServerHello (stVersion st) srand (Session Nothing) (cipherID cipher) 0 Nothing
let (_,cert,privkeycert) = fromJust $ spCertificate sp
let srvcert = Certificates [ cert ]
-- in TLS12, we need to check as well the certificates we are sending if they have in the extension
-- the necessary bits set.
let needkeyxchg = cipherExchangeNeedMoreData $ cipherKeyExchange cipher
let privkey = PrivateKey
{ private_size = fromIntegral $ CertificateKey.privKey_lenmodulus privkeycert
, private_n = CertificateKey.privKey_modulus privkeycert
, private_d = CertificateKey.privKey_private_exponant privkeycert
}
setPrivateKey privkey
sendPacket handle (Handshake srvhello)
sendPacket handle (Handshake srvcert)
when needkeyxchg $ do
let skg = SKX_RSA Nothing
sendPacket handle (Handshake $ ServerKeyXchg skg)
-- FIXME we don't do this on a Anonyous server
when (spWantClientCert sp) $ do
let certTypes = [ CertificateType_RSA_Sign ]
let creq = CertRequest certTypes Nothing [0,0,0]
sendPacket handle (Handshake creq)
sendPacket handle (Handshake ServerHelloDone)
handshakeSendFinish :: Handle -> TLSServer IO ()
handshakeSendFinish handle = do
cf <- getHandshakeDigest False
sendPacket handle (Handshake $ Finished $ L.unpack cf)
{- after receiving a client hello, we need to redo a handshake -}
handshake :: Handle -> ServerRandom -> TLSServer IO ()
handshake handle srand = do
handshakeSendServerData handle srand
lift $ hFlush handle
recvPacket handle >>= \pkt -> expectingHandshake pkt HandshakeType_ClientKeyXchg
recvPacket handle >>= \pkt -> expectingPacket pkt ProtocolType_ChangeCipherSpec
recvPacket handle >>= \pkt -> expectingHandshake pkt HandshakeType_Finished
sendPacket handle ChangeCipherSpec
handshakeSendFinish handle
lift $ hFlush handle
return ()
{- | listen on a handle to a new TLS connection. -}
listen :: Handle -> ServerRandom -> TLSServer IO ()
listen handle srand = do
pkt <- recvPacket handle
case pkt of
Right (Handshake hs) -> handleClientHello hs
x -> fail ("unexpected type received. expecting handshake ++ " ++ show x)
handshake handle srand
return ()
{- | sendData sends a bunch of data -}
sendData :: Handle -> L.ByteString -> TLSServer IO ()
sendData handle d =
if L.length d > 16384
then do
let (sending, remain) = L.splitAt 16384 d
sendPacket handle $ AppData sending
sendData handle remain
else
sendPacket handle $ AppData d
{- | recvData get data out of Data packet, and automatically renegociate if
- a Handshake ClientHello is received -}
recvData :: Handle -> TLSServer IO L.ByteString
recvData handle = do
pkt <- recvPacket handle
case pkt of
Right (Handshake (ClientHello _ _ _ _ _ _)) -> do
-- SECURITY FIXME audit the rng here..
st <- getTLSState
let (bytes, rng') = getRandomBytes (stRandomGen st) 32
putTLSState $ st { stRandomGen = rng' }
let srand = fromJust $ serverRandom bytes
handshake handle srand
recvData handle
Right (AppData x) -> return x
Left err -> error ("error received: " ++ show err)
_ -> error "unexpected item"
{- | close a TLS connection.
- note that it doesn't close the handle, but just signal we're going to close
- the connection to the other side -}
close :: Handle -> TLSServer IO ()
close handle = do
sendPacket handle $ Alert (AlertLevel_Warning, CloseNotify)

271
Network/TLS/State.hs Normal file
View file

@ -0,0 +1,271 @@
-- |
-- Module : Network.TLS.State
-- License : BSD-style
-- Maintainer : Vincent Hanquez <vincent@snarc.org>
-- Stability : experimental
-- Portability : unknown
--
-- the State module contains calls related to state initialization/manipulation
-- which is use by the Receiving module and the Sending module.
--
module Network.TLS.State
( TLSState(..)
, TLSHandshakeState(..)
, TLSCryptState(..)
, TLSMacState(..)
, MonadTLSState, getTLSState, putTLSState, modifyTLSState
, newTLSState
, assert -- FIXME move somewhere else (Internal.hs ?)
, finishHandshakeTypeMaterial
, finishHandshakeMaterial
, makeDigest
, setMasterSecret
, setPublicKey
, setPrivateKey
, setKeyBlock
, setVersion
, setCipher
, setServerRandom
, switchTxEncryption
, switchRxEncryption
, isClientContext
, startHandshakeClient
, updateHandshakeDigest
, getHandshakeDigest
, endHandshake
) where
import Data.Word
import Data.Maybe (fromJust, isNothing)
import Network.TLS.Struct
import Network.TLS.SRandom
import Network.TLS.Wire
import Network.TLS.Packet
import Network.TLS.Crypto
import Network.TLS.Cipher
import Data.ByteString.Lazy (ByteString)
import qualified Data.ByteString.Lazy as L
import Control.Monad
assert :: Monad m => String -> [(String,Bool)] -> m ()
assert fctname list = forM_ list $ \ (name, assumption) -> do
when assumption $ fail (fctname ++ ": assumption about " ++ name ++ " failed")
data TLSCryptState = TLSCryptState
{ cstKey :: ![Word8]
, cstIV :: ![Word8]
, cstMacSecret :: L.ByteString
} deriving (Show)
data TLSMacState = TLSMacState
{ msSequence :: Word64
} deriving (Show)
data TLSHandshakeState = TLSHandshakeState
{ hstClientVersion :: !(Version)
, hstClientRandom :: !ClientRandom
, hstServerRandom :: !(Maybe ServerRandom)
, hstMasterSecret :: !(Maybe [Word8])
, hstRSAPublicKey :: !(Maybe PublicKey)
, hstRSAPrivateKey :: !(Maybe PrivateKey)
, hstHandshakeDigest :: Maybe (HashCtx, HashCtx) -- FIXME could be only 1 hash in tls12
} deriving (Show)
data TLSState = TLSState
{ stClientContext :: Bool
, stClientVersion :: !(Maybe Version)
, stVersion :: !Version
, stHandshake :: !(Maybe TLSHandshakeState)
, stTxEncrypted :: Bool
, stRxEncrypted :: Bool
, stTxCryptState :: !(Maybe TLSCryptState)
, stRxCryptState :: !(Maybe TLSCryptState)
, stTxMacState :: !(Maybe TLSMacState)
, stRxMacState :: !(Maybe TLSMacState)
, stCipher :: Maybe Cipher
, stRandomGen :: SRandomGen
} deriving (Show)
class (Monad m) => MonadTLSState m where
getTLSState :: m TLSState
putTLSState :: TLSState -> m ()
newTLSState :: SRandomGen -> TLSState
newTLSState rng = TLSState
{ stClientContext = False
, stClientVersion = Nothing
, stVersion = TLS10
, stHandshake = Nothing
, stTxEncrypted = False
, stRxEncrypted = False
, stTxCryptState = Nothing
, stRxCryptState = Nothing
, stTxMacState = Nothing
, stRxMacState = Nothing
, stCipher = Nothing
, stRandomGen = rng
}
modifyTLSState :: (MonadTLSState m) => (TLSState -> TLSState) -> m ()
modifyTLSState f = getTLSState >>= \st -> putTLSState (f st)
makeDigest :: (MonadTLSState m) => Bool -> Header -> ByteString -> m ByteString
makeDigest w hdr content = do
st <- getTLSState
assert "make digest"
[ ("cipher", isNothing $ stCipher st)
, ("crypt state", isNothing $ if w then stTxCryptState st else stRxCryptState st)
, ("mac state", isNothing $ if w then stTxMacState st else stRxMacState st) ]
let cst = fromJust $ if w then stTxCryptState st else stRxCryptState st
let ms = fromJust $ if w then stTxMacState st else stRxMacState st
let cipher = fromJust $ stCipher st
let hmac_msg = L.concat [ encodeWord64 $ msSequence ms, encodeHeader hdr, content ]
let digest = (cipherHMAC cipher) (cstMacSecret cst) hmac_msg
let newms = ms { msSequence = (msSequence ms) + 1 }
modifyTLSState (\_ -> if w then st { stTxMacState = Just newms } else st { stRxMacState = Just newms })
return digest
finishHandshakeTypeMaterial :: HandshakeType -> Bool
finishHandshakeTypeMaterial HandshakeType_ClientHello = True
finishHandshakeTypeMaterial HandshakeType_ServerHello = True
finishHandshakeTypeMaterial HandshakeType_Certificate = True
finishHandshakeTypeMaterial HandshakeType_HelloRequest = False
finishHandshakeTypeMaterial HandshakeType_ServerHelloDone = True
finishHandshakeTypeMaterial HandshakeType_ClientKeyXchg = True
finishHandshakeTypeMaterial HandshakeType_ServerKeyXchg = True
finishHandshakeTypeMaterial HandshakeType_CertRequest = True
finishHandshakeTypeMaterial HandshakeType_CertVerify = False
finishHandshakeTypeMaterial HandshakeType_Finished = True
finishHandshakeMaterial :: Handshake -> Bool
finishHandshakeMaterial = finishHandshakeTypeMaterial . typeOfHandshake
switchTxEncryption :: MonadTLSState m => m ()
switchTxEncryption = getTLSState >>= putTLSState . (\st -> st { stTxEncrypted = True })
switchRxEncryption :: MonadTLSState m => m ()
switchRxEncryption = getTLSState >>= putTLSState . (\st -> st { stRxEncrypted = True })
setServerRandom :: MonadTLSState m => ServerRandom -> m ()
setServerRandom ran = updateHandshake "srand" (\hst -> hst { hstServerRandom = Just ran })
setMasterSecret :: MonadTLSState m => ByteString -> m ()
setMasterSecret premastersecret = do
st <- getTLSState
hasValidHandshake "master secret"
assert "set master secret"
[ ("server random", (isNothing $ hstServerRandom $ fromJust $ stHandshake st)) ]
updateHandshake "master secret" (\hst ->
let ms = generateMasterSecret premastersecret (hstClientRandom hst) (fromJust $ hstServerRandom hst) in
hst { hstMasterSecret = Just $ L.unpack ms } )
return ()
setPublicKey :: MonadTLSState m => PublicKey -> m ()
setPublicKey pk = updateHandshake "publickey" (\hst -> hst { hstRSAPublicKey = Just pk })
setPrivateKey :: MonadTLSState m => PrivateKey -> m ()
setPrivateKey pk = updateHandshake "privatekey" (\hst -> hst { hstRSAPrivateKey = Just pk })
setKeyBlock :: MonadTLSState m => m ()
setKeyBlock = do
st <- getTLSState
let hst = fromJust $ stHandshake st
assert "set key block"
[ ("cipher", (isNothing $ stCipher st))
, ("server random", (isNothing $ hstServerRandom hst))
, ("master secret", (isNothing $ hstMasterSecret hst))
]
let cc = stClientContext st
let cipher = fromJust $ stCipher st
let keyblockSize = fromIntegral $ cipherKeyBlockSize cipher
let digestSize = cipherDigestSize cipher
let keySize = cipherKeySize cipher
let ivSize = cipherIVSize cipher
let kb = generateKeyBlock (hstClientRandom hst)
(fromJust $ hstServerRandom hst)
(L.pack $ fromJust $ hstMasterSecret hst) keyblockSize
let (cMACSecret, r1) = L.splitAt (fromIntegral digestSize) kb
let (sMACSecret, r2) = L.splitAt (fromIntegral digestSize) r1
let (cWriteKey, r3) = L.splitAt (fromIntegral keySize) r2
let (sWriteKey, r4) = L.splitAt (fromIntegral keySize) r3
let (cWriteIV, r5) = L.splitAt (fromIntegral ivSize) r4
let (sWriteIV, _) = L.splitAt (fromIntegral ivSize) r5
let cstClient = TLSCryptState
{ cstKey = L.unpack cWriteKey
, cstIV = L.unpack cWriteIV
, cstMacSecret = cMACSecret }
let cstServer = TLSCryptState
{ cstKey = L.unpack sWriteKey
, cstIV = L.unpack sWriteIV
, cstMacSecret = sMACSecret }
let msClient = TLSMacState { msSequence = 0 }
let msServer = TLSMacState { msSequence = 0 }
putTLSState $ st
{ stTxCryptState = Just $ if cc then cstClient else cstServer
, stRxCryptState = Just $ if cc then cstServer else cstClient
, stTxMacState = Just $ if cc then msClient else msServer
, stRxMacState = Just $ if cc then msServer else msClient
}
setCipher :: MonadTLSState m => Cipher -> m ()
setCipher cipher = getTLSState >>= putTLSState . (\st -> st { stCipher = Just cipher })
setVersion :: MonadTLSState m => Version -> m ()
setVersion ver = getTLSState >>= putTLSState . (\st -> st { stVersion = ver })
isClientContext :: MonadTLSState m => m Bool
isClientContext = getTLSState >>= return . stClientContext
-- create a new empty handshake state
newEmptyHandshake :: Version -> ClientRandom -> TLSHandshakeState
newEmptyHandshake ver crand = TLSHandshakeState
{ hstClientVersion = ver
, hstClientRandom = crand
, hstServerRandom = Nothing
, hstMasterSecret = Nothing
, hstRSAPublicKey = Nothing
, hstRSAPrivateKey = Nothing
, hstHandshakeDigest = Nothing
}
startHandshakeClient :: MonadTLSState m => Version -> ClientRandom -> m ()
startHandshakeClient ver crand = do
-- FIXME check if handshake is already not null
chs <- getTLSState >>= return . stHandshake
when (isNothing chs) $
modifyTLSState (\st -> st { stHandshake = Just $ newEmptyHandshake ver crand })
hasValidHandshake :: MonadTLSState m => String -> m ()
hasValidHandshake name = getTLSState >>= \st -> assert name [ ("valid handshake", isNothing $ stHandshake st) ]
updateHandshake :: MonadTLSState m => String -> (TLSHandshakeState -> TLSHandshakeState) -> m ()
updateHandshake n f = do
hasValidHandshake n
modifyTLSState (\st -> st { stHandshake = maybe Nothing (Just . f) (stHandshake st) })
updateHandshakeDigest :: MonadTLSState m => ByteString -> m ()
updateHandshakeDigest content = updateHandshake "update digest" (\hs ->
let ctxs = case hstHandshakeDigest hs of
Nothing -> (initHash HashTypeSHA1, initHash HashTypeMD5)
Just (sha1ctx, md5ctx) -> (sha1ctx, md5ctx) in
let (nc1, nc2) = foldl (\(c1, c2) s -> (updateHash c1 s, updateHash c2 s)) ctxs $ L.toChunks content in
hs { hstHandshakeDigest = Just (nc1, nc2) }
)
getHandshakeDigest :: MonadTLSState m => Bool -> m ByteString
getHandshakeDigest client = do
st <- getTLSState
let hst = fromJust $ stHandshake st
let (sha1ctx, md5ctx) = fromJust $ hstHandshakeDigest hst
let msecret = fromJust $ hstMasterSecret hst
return $ (if client then generateClientFinished else generateServerFinished) (L.pack msecret) md5ctx sha1ctx
endHandshake :: MonadTLSState m => m ()
endHandshake = modifyTLSState (\st -> st { stHandshake = Nothing })

404
Network/TLS/Struct.hs Normal file
View file

@ -0,0 +1,404 @@
-- |
-- Module : Network.TLS.Struct
-- License : BSD-style
-- Maintainer : Vincent Hanquez <vincent@snarc.org>
-- Stability : experimental
-- Portability : unknown
--
-- the Struct module contains all definitions and values of the TLS protocol
--
module Network.TLS.Struct
( Version(..)
, ConnectionEnd(..)
, CipherType(..)
, Extension
, EncryptedData(..)
, CertificateType(..)
, HashAlgorithm(..)
, SignatureAlgorithm(..)
, ProtocolType(..)
, TLSError(..)
, ServerDHParams(..)
, ServerRSAParams(..)
, ServerKeyXchgAlgorithmData(..)
, Packet(..)
, Header(..)
, ServerRandom(..)
, ClientRandom(..)
, serverRandom
, clientRandom
, FinishedData
, Session(..)
, AlertLevel(..)
, AlertDescription(..)
, HandshakeType(..)
, Handshake(..)
, numericalVer
, verOfNum
, TypeValuable, valOfType, valToType
, packetType
, typeOfHandshake
) where
import Data.ByteString.Lazy (ByteString)
import Data.Word
import Data.Certificate.X509
data Version = SSL2 | SSL3 | TLS10 | TLS11 | TLS12 deriving (Show, Eq, Ord)
data ConnectionEnd = ConnectionServer | ConnectionClient
data CipherType = CipherStream | CipherBlock | CipherAEAD
data CertificateType =
CertificateType_RSA_Sign -- TLS10
| CertificateType_DSS_Sign -- TLS10
| CertificateType_RSA_Fixed_DH -- TLS10
| CertificateType_DSS_Fixed_DH -- TLS10
| CertificateType_RSA_Ephemeral_dh -- TLS12
| CertificateType_DSS_Ephemeral_dh -- TLS12
| CertificateType_fortezza_dms -- TLS12
| CertificateType_Unknown Word8
deriving (Show,Eq)
data HashAlgorithm =
HashNone
| HashMD5
| HashSHA1
| HashSHA224
| HashSHA256
| HashSHA384
| HashSHA512
| HashOther Word8
deriving (Show,Eq)
data SignatureAlgorithm =
SignatureAnonymous
| SignatureRSA
| SignatureDSS
| SignatureECDSA
| SignatureOther Word8
deriving (Show,Eq)
data ProtocolType =
ProtocolType_ChangeCipherSpec
| ProtocolType_Alert
| ProtocolType_Handshake
| ProtocolType_AppData
deriving (Eq, Show)
data TLSError =
Error_Misc String
| Error_Certificate String
| Error_Digest ([Word8], [Word8])
| Error_Packet String
| Error_Packet_Size_Mismatch (Int, Int)
| Error_Internal_Packet_Remaining Int
| Error_Internal_Packet_ByteProcessed Int Int Int
| Error_Unknown_Version Word8 Word8
| Error_Unknown_Type String
deriving (Eq, Show)
data Packet =
Handshake Handshake
| Alert (AlertLevel, AlertDescription)
| ChangeCipherSpec
| AppData ByteString
deriving (Show,Eq)
data Header = Header ProtocolType Version Word16 deriving (Show, Eq)
newtype ServerRandom = ServerRandom [Word8] deriving (Show, Eq)
newtype ClientRandom = ClientRandom [Word8] deriving (Show, Eq)
newtype Session = Session (Maybe [Word8]) deriving (Show, Eq)
type CipherID = Word16
type CompressionID = Word8
type FinishedData = [Word8]
type Extension = (Word16, [Word8])
constrRandom32 :: ([Word8] -> a) -> [Word8] -> Maybe a
constrRandom32 constr l = if length l == 32 then Just (constr l) else Nothing
serverRandom :: [Word8] -> Maybe ServerRandom
serverRandom l = constrRandom32 ServerRandom l
clientRandom :: [Word8] -> Maybe ClientRandom
clientRandom l = constrRandom32 ClientRandom l
newtype EncryptedData = EncryptedData ByteString
deriving (Show)
data AlertLevel =
AlertLevel_Warning
| AlertLevel_Fatal
deriving (Show,Eq)
data AlertDescription =
CloseNotify
| UnexpectedMessage
| BadRecordMac
| DecryptionFailed
| RecordOverflow
| DecompressionFailure
| HandshakeFailure
| BadCertificate
| UnsupportedCertificate
| CertificateRevoked
| CertificateExpired
| CertificateUnknown
| IllegalParameter
| UnknownCa
| AccessDenied
| DecodeError
| DecryptError
| ExportRestriction
| ProtocolVersion
| InsufficientSecurity
| InternalError
| UserCanceled
| NoRenegotiation
deriving (Show,Eq)
data HandshakeType =
HandshakeType_HelloRequest
| HandshakeType_ClientHello
| HandshakeType_ServerHello
| HandshakeType_Certificate
| HandshakeType_ServerKeyXchg
| HandshakeType_CertRequest
| HandshakeType_ServerHelloDone
| HandshakeType_CertVerify
| HandshakeType_ClientKeyXchg
| HandshakeType_Finished
deriving (Show,Eq)
data ServerDHParams = ServerDHParams
{ dh_p :: Integer -- ^ prime modulus
, dh_g :: Integer -- ^ generator
, dh_Ys :: Integer -- ^ public value (g^X mod p)
} deriving (Show,Eq)
data ServerRSAParams = ServerRSAParams
{ rsa_modulus :: Integer
, rsa_exponent :: Integer
} deriving (Show,Eq)
data ServerKeyXchgAlgorithmData =
SKX_DH_Anon ServerDHParams
| SKX_DHE_DSS ServerDHParams [Word8]
| SKX_DHE_RSA ServerDHParams [Word8]
| SKX_RSA (Maybe ServerRSAParams)
| SKX_DH_DSS (Maybe ServerRSAParams)
| SKX_DH_RSA (Maybe ServerRSAParams)
deriving (Show,Eq)
data Handshake =
ClientHello !Version !ClientRandom !Session ![CipherID] ![CompressionID] (Maybe [Extension])
| ServerHello !Version !ServerRandom !Session !CipherID !CompressionID (Maybe [Extension])
| Certificates [Certificate]
| HelloRequest
| ServerHelloDone
| ClientKeyXchg Version [Word8]
| ServerKeyXchg ServerKeyXchgAlgorithmData
| CertRequest [CertificateType] (Maybe [ (HashAlgorithm, SignatureAlgorithm) ]) [Word8]
| CertVerify [Word8]
| Finished FinishedData
deriving (Show,Eq)
packetType :: Packet -> ProtocolType
packetType (Handshake _) = ProtocolType_Handshake
packetType (Alert _) = ProtocolType_Alert
packetType ChangeCipherSpec = ProtocolType_ChangeCipherSpec
packetType (AppData _) = ProtocolType_AppData
typeOfHandshake :: Handshake -> HandshakeType
typeOfHandshake (ClientHello _ _ _ _ _ _) = HandshakeType_ClientHello
typeOfHandshake (ServerHello _ _ _ _ _ _) = HandshakeType_ServerHello
typeOfHandshake (Certificates _) = HandshakeType_Certificate
typeOfHandshake (HelloRequest) = HandshakeType_HelloRequest
typeOfHandshake (ServerHelloDone) = HandshakeType_ServerHelloDone
typeOfHandshake (ClientKeyXchg _ _) = HandshakeType_ClientKeyXchg
typeOfHandshake (ServerKeyXchg _) = HandshakeType_ServerKeyXchg
typeOfHandshake (CertRequest _ _ _) = HandshakeType_CertRequest
typeOfHandshake (CertVerify _) = HandshakeType_CertVerify
typeOfHandshake (Finished _) = HandshakeType_Finished
numericalVer :: Version -> (Word8, Word8)
numericalVer SSL2 = (2, 0)
numericalVer SSL3 = (3, 0)
numericalVer TLS10 = (3, 1)
numericalVer TLS11 = (3, 2)
numericalVer TLS12 = (3, 3)
verOfNum :: (Word8, Word8) -> Maybe Version
verOfNum (2, 0) = Just SSL2
verOfNum (3, 0) = Just SSL3
verOfNum (3, 1) = Just TLS10
verOfNum (3, 2) = Just TLS11
verOfNum (3, 3) = Just TLS12
verOfNum _ = Nothing
class TypeValuable a where
valOfType :: a -> Word8
valToType :: Word8 -> Maybe a
instance TypeValuable ConnectionEnd where
valOfType ConnectionServer = 0
valOfType ConnectionClient = 1
valToType 0 = Just ConnectionServer
valToType 1 = Just ConnectionClient
valToType _ = Nothing
instance TypeValuable CipherType where
valOfType CipherStream = 0
valOfType CipherBlock = 1
valOfType CipherAEAD = 2
valToType 0 = Just CipherStream
valToType 1 = Just CipherBlock
valToType 2 = Just CipherAEAD
valToType _ = Nothing
instance TypeValuable ProtocolType where
valOfType ProtocolType_ChangeCipherSpec = 20
valOfType ProtocolType_Alert = 21
valOfType ProtocolType_Handshake = 22
valOfType ProtocolType_AppData = 23
valToType 20 = Just ProtocolType_ChangeCipherSpec
valToType 21 = Just ProtocolType_Alert
valToType 22 = Just ProtocolType_Handshake
valToType 23 = Just ProtocolType_AppData
valToType _ = Nothing
instance TypeValuable HandshakeType where
valOfType HandshakeType_HelloRequest = 0
valOfType HandshakeType_ClientHello = 1
valOfType HandshakeType_ServerHello = 2
valOfType HandshakeType_Certificate = 11
valOfType HandshakeType_ServerKeyXchg = 12
valOfType HandshakeType_CertRequest = 13
valOfType HandshakeType_ServerHelloDone = 14
valOfType HandshakeType_CertVerify = 15
valOfType HandshakeType_ClientKeyXchg = 16
valOfType HandshakeType_Finished = 20
valToType 0 = Just HandshakeType_HelloRequest
valToType 1 = Just HandshakeType_ClientHello
valToType 2 = Just HandshakeType_ServerHello
valToType 11 = Just HandshakeType_Certificate
valToType 12 = Just HandshakeType_ServerKeyXchg
valToType 13 = Just HandshakeType_CertRequest
valToType 14 = Just HandshakeType_ServerHelloDone
valToType 15 = Just HandshakeType_CertVerify
valToType 16 = Just HandshakeType_ClientKeyXchg
valToType 20 = Just HandshakeType_Finished
valToType _ = Nothing
instance TypeValuable AlertLevel where
valOfType AlertLevel_Warning = 1
valOfType AlertLevel_Fatal = 2
valToType 1 = Just AlertLevel_Warning
valToType 2 = Just AlertLevel_Fatal
valToType _ = Nothing
instance TypeValuable AlertDescription where
valOfType CloseNotify = 0
valOfType UnexpectedMessage = 10
valOfType BadRecordMac = 20
valOfType DecryptionFailed = 21
valOfType RecordOverflow = 22
valOfType DecompressionFailure = 30
valOfType HandshakeFailure = 40
valOfType BadCertificate = 42
valOfType UnsupportedCertificate = 43
valOfType CertificateRevoked = 44
valOfType CertificateExpired = 45
valOfType CertificateUnknown = 46
valOfType IllegalParameter = 47
valOfType UnknownCa = 48
valOfType AccessDenied = 49
valOfType DecodeError = 50
valOfType DecryptError = 51
valOfType ExportRestriction = 60
valOfType ProtocolVersion = 70
valOfType InsufficientSecurity = 71
valOfType InternalError = 80
valOfType UserCanceled = 90
valOfType NoRenegotiation = 100
valToType 0 = Just CloseNotify
valToType 10 = Just UnexpectedMessage
valToType 20 = Just BadRecordMac
valToType 21 = Just DecryptionFailed
valToType 22 = Just RecordOverflow
valToType 30 = Just DecompressionFailure
valToType 40 = Just HandshakeFailure
valToType 42 = Just BadCertificate
valToType 43 = Just UnsupportedCertificate
valToType 44 = Just CertificateRevoked
valToType 45 = Just CertificateExpired
valToType 46 = Just CertificateUnknown
valToType 47 = Just IllegalParameter
valToType 48 = Just UnknownCa
valToType 49 = Just AccessDenied
valToType 50 = Just DecodeError
valToType 51 = Just DecryptError
valToType 60 = Just ExportRestriction
valToType 70 = Just ProtocolVersion
valToType 71 = Just InsufficientSecurity
valToType 80 = Just InternalError
valToType 90 = Just UserCanceled
valToType 100 = Just NoRenegotiation
valToType _ = Nothing
instance TypeValuable CertificateType where
valOfType CertificateType_RSA_Sign = 1
valOfType CertificateType_DSS_Sign = 2
valOfType CertificateType_RSA_Fixed_DH = 3
valOfType CertificateType_DSS_Fixed_DH = 4
valOfType CertificateType_RSA_Ephemeral_dh = 5
valOfType CertificateType_DSS_Ephemeral_dh = 6
valOfType CertificateType_fortezza_dms = 20
valOfType (CertificateType_Unknown i) = i
valToType 1 = Just CertificateType_RSA_Sign
valToType 2 = Just CertificateType_DSS_Sign
valToType 3 = Just CertificateType_RSA_Fixed_DH
valToType 4 = Just CertificateType_DSS_Fixed_DH
valToType 5 = Just CertificateType_RSA_Ephemeral_dh
valToType 6 = Just CertificateType_DSS_Ephemeral_dh
valToType 20 = Just CertificateType_fortezza_dms
valToType i = Just (CertificateType_Unknown i)
instance TypeValuable HashAlgorithm where
valOfType HashNone = 0
valOfType HashMD5 = 1
valOfType HashSHA1 = 2
valOfType HashSHA224 = 3
valOfType HashSHA256 = 4
valOfType HashSHA384 = 5
valOfType HashSHA512 = 6
valOfType (HashOther i) = i
valToType 0 = Just HashNone
valToType 1 = Just HashMD5
valToType 2 = Just HashSHA1
valToType 3 = Just HashSHA224
valToType 4 = Just HashSHA256
valToType 5 = Just HashSHA384
valToType 6 = Just HashSHA512
valToType i = Just (HashOther i)
instance TypeValuable SignatureAlgorithm where
valOfType SignatureAnonymous = 0
valOfType SignatureRSA = 1
valOfType SignatureDSS = 2
valOfType SignatureECDSA = 3
valOfType (SignatureOther i) = i
valToType 0 = Just SignatureAnonymous
valToType 1 = Just SignatureRSA
valToType 2 = Just SignatureDSS
valToType 3 = Just SignatureECDSA
valToType i = Just (SignatureOther i)

124
Network/TLS/Wire.hs Normal file
View file

@ -0,0 +1,124 @@
{-# LANGUAGE GeneralizedNewtypeDeriving,FlexibleInstances #-}
-- |
-- Module : Network.TLS.Wire
-- License : BSD-style
-- Maintainer : Vincent Hanquez <vincent@snarc.org>
-- Stability : experimental
-- Portability : unknown
--
-- the Wire module is a specialized Binary package related to the TLS protocol.
-- all multibytes values are written as big endian.
--
module Network.TLS.Wire
( Get
, runGet
, remaining
, bytesRead
, getWord8
, getWords8
, getWord16
, getWords16
, getWord24
, getBytes
, processBytes
, isEmpty
, Put
, runPut
, putWord8
, putWords8
, putWord16
, putWords16
, putWord24
, putByteString
, putLazyByteString
, encodeWord64
) where
import qualified Data.Binary.Get as Bin
import Data.Binary.Put
import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy as L
import Control.Monad.Error
import Data.Word
import Data.Bits
import Network.TLS.Struct
instance Error TLSError where
noMsg = Error_Misc ""
strMsg = Error_Misc
newtype Get a = GE { runGE :: ErrorT TLSError Bin.Get a }
deriving (Monad, MonadError TLSError)
instance Functor Get where
fmap f = GE . fmap f . runGE
liftGet :: Bin.Get a -> Get a
liftGet = GE . lift
runGet :: Get a -> L.ByteString -> Either TLSError a
runGet f b = Bin.runGet (runErrorT (runGE f)) b
remaining :: Get Int
remaining = fmap fromIntegral $ liftGet Bin.remaining
bytesRead :: Get Int
bytesRead = fmap fromIntegral $ liftGet Bin.bytesRead
getWord8 :: Get Word8
getWord8 = liftGet Bin.getWord8
getWords8 :: Get [Word8]
getWords8 = getWord8 >>= \lenb -> replicateM (fromIntegral lenb) getWord8
getWord16 :: Get Word16
getWord16 = liftGet Bin.getWord16be
getWords16 :: Get [Word16]
getWords16 = getWord16 >>= \lenb -> replicateM (fromIntegral lenb `div` 2) getWord16
getWord24 :: Get Int
getWord24 = do
a <- fmap fromIntegral getWord8
b <- fmap fromIntegral getWord8
c <- fmap fromIntegral getWord8
return $ (a `shiftL` 16) .|. (b `shiftL` 8) .|. c
getBytes :: Int -> Get ByteString
getBytes i = liftGet $ Bin.getBytes i
processBytes :: Int -> Get a -> Get a
processBytes i f = do
r1 <- bytesRead
ret <- f
r2 <- bytesRead
if r2 == (r1 + i)
then return ret
else throwError (Error_Internal_Packet_ByteProcessed r1 r2 i)
isEmpty :: Get Bool
isEmpty = liftGet Bin.isEmpty
putWords8 :: [Word8] -> Put
putWords8 l = do
putWord8 $ fromIntegral (length l)
mapM_ putWord8 l
putWord16 :: Word16 -> Put
putWord16 = putWord16be
putWords16 :: [Word16] -> Put
putWords16 l = do
putWord16 $ 2 * (fromIntegral $ length l)
mapM_ putWord16 l
putWord24 :: Int -> Put
putWord24 i = do
let a = fromIntegral ((i `shiftR` 16) .&. 0xff)
let b = fromIntegral ((i `shiftR` 8) .&. 0xff)
let c = fromIntegral (i .&. 0xff)
mapM_ putWord8 [a,b,c]
encodeWord64 :: Word64 -> L.ByteString
encodeWord64 = runPut . putWord64be

7
README Normal file
View file

@ -0,0 +1,7 @@
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)

2
Setup.hs Normal file
View file

@ -0,0 +1,2 @@
import Distribution.Simple
main = defaultMain

149
Stunnel.hs Normal file
View file

@ -0,0 +1,149 @@
import Network
import System.IO
import System
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString.Lazy.Char8 as LC
import Control.Exception (bracket)
import Network.TLS.Cipher
import qualified Network.TLS.Client as C
import qualified Network.TLS.Server as S
import Network.TLS.SRandom
import Network.TLS.Struct
import Network.TLS.MAC
import Data.Word
import Data.Bits
import Data.Maybe
import Control.Monad (forM_, when, replicateM)
import Control.Monad.Trans (lift)
import Random
import qualified Codec.Crypto.AES.Random as AESRand
import Control.Concurrent (forkIO)
import Data.Certificate.PEM
import Data.Certificate.X509
import Data.Certificate.Key
ciphers :: [Cipher]
ciphers =
[ cipher_AES128_SHA1
, cipher_AES256_SHA1
, cipher_RC4_128_MD5
, cipher_RC4_128_SHA1
]
conv :: [Word8] -> Int
conv l = (a `shiftL` 24) .|. (b `shiftL` 16) .|. (c `shiftL` 8) .|. d
where
[a,b,c,d] = map fromIntegral l
tlsclient handle crand prerand = do
C.connect handle crand prerand
C.sendData handle (L.pack $ map (toEnum.fromEnum) "GET / HTTP/1.0\r\n\r\n")
d <- C.recvData handle
lift $ L.putStrLn d
d <- C.recvData handle
lift $ L.putStrLn d
return ()
mainClient :: String -> Int -> IO ()
mainClient host port = do
{- generate some random stuff ready to be used after skipping some byte for no particular reason -}
ranByte <- fmap B.head $ AESRand.randBytes 1
_ <- AESRand.randBytes (fromIntegral ranByte)
clientRandom <- fmap (fromJust . clientRandom . B.unpack) $ AESRand.randBytes 32
premasterRandom <- fmap B.unpack $ AESRand.randBytes 46
seqInit <- fmap (conv . B.unpack) $ AESRand.randBytes 4
handle <- connectTo host (PortNumber 6061)
hSetBuffering handle NoBuffering
let clientstate = C.TLSClientParams
{ C.cpConnectVersion = TLS10
, C.cpAllowedVersions = [ TLS10 ]
, C.cpSession = Nothing
, C.cpCiphers = ciphers
, C.cpCertificate = Nothing
}
C.runTLSClient (tlsclient handle clientRandom premasterRandom) clientstate (makeSRandomGen seqInit)
putStrLn "end"
tlsserver handle srand = do
S.listen handle srand
_ <- S.recvData handle
S.sendData handle (LC.pack "this is some data")
lift $ hFlush handle
lift $ putStrLn "end"
clientProcess ((certdata, cert), pk) (handle, src) = do
serverRandom <- fmap (fromJust . serverRandom . B.unpack) $ AESRand.randBytes 32
seqInit <- fmap (conv . B.unpack) $ AESRand.randBytes 4
let serverstate = S.TLSServerParams
{ S.spAllowedVersions = [TLS10]
, S.spSessions = []
, S.spCiphers = ciphers
, S.spCertificate = Just (certdata, cert, pk)
, S.spWantClientCert = False
}
S.runTLSServer (tlsserver handle serverRandom) serverstate (makeSRandomGen seqInit)
putStrLn "end"
mainServerAccept cert port socket = do
(h, d, _) <- accept socket
forkIO $ clientProcess cert (h, d)
mainServerAccept cert port socket
mainServer cert port = bracket (listenOn (PortNumber port)) (sClose) (mainServerAccept cert port)
usage :: IO ()
usage = do
putStrLn "usage: stunnel [client|server] <params...>"
exitFailure
readCertificate :: FilePath -> IO (L.ByteString, Certificate)
readCertificate filepath = do
content <- B.readFile filepath
let certdata = case parsePEMCert content of
Left err -> error ("cannot read PEM certificate: " ++ err)
Right x -> L.fromChunks [x]
let cert = case decodeCertificate certdata of
Left err -> error ("cannot decode certificate: " ++ err)
Right x -> x
return (certdata, cert)
readPrivateKey :: FilePath -> IO (L.ByteString, PrivateKey)
readPrivateKey filepath = do
content <- B.readFile filepath
let pkdata = case parsePEMKey content of
Left err -> error ("cannot read PEM key: " ++ err)
Right x -> L.fromChunks [x]
let pk = case decodePrivateKey pkdata of
Left err -> error ("cannot decode key: " ++ err)
Right x -> x
return (pkdata, pk)
main = do
args <- getArgs
when (length args == 0) usage
case (args !! 0) of
"server" -> do
cert <- readCertificate (args !! 1)
pk <- readPrivateKey (args !! 2)
mainServer (cert, snd pk) 6061
"client" -> do
let port =
if length args > 1
then read $ args !! 1
else 6061
let dest =
if length args > 2
then args !! 2
else "localhost"
mainClient dest port
_ -> usage

44
TODO Normal file
View file

@ -0,0 +1,44 @@
protocol:
- finish implementing renegocitiation Client and Server
- implement Certificate Verify / Certificate Request
- add Client Certificates
- add check for non-self signed certificate
- alert correctly on errors
- 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:
- finish implementation of extensions
- 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
code cleanup:
- remove show derivation on internal crypto state
- opaquify differents data type through newtype
security audit:
- add unit tests for pure parts
- fix SRandomGen and random usage with proper CPRNG
- match security recommendation from the RFC
- audit the RSA implementation and the usage in TLS (remove spoon).
misc:
- verify it works with gnutls
- stunnel: use crypto secure random generator
- stunnel: actually make it works like stunnel instead of hardcoding the data and the port.
- investigate an iteratee interface
- portability
- implement more ciphers
- check & optimize memory footprint
- compare & optimize performance

85
Tests.hs Normal file
View file

@ -0,0 +1,85 @@
import Text.Printf
import Data.Word
import Test.QuickCheck
import Test.QuickCheck.Batch
import Network.TLS.Struct
import Network.TLS.Packet
import Control.Monad
liftM6 f m1 m2 m3 m4 m5 m6 = do { x1 <- m1; x2 <- m2; x3 <- m3; x4 <- m4; x5 <- m5; x6 <- m6; return (f x1 x2 x3 x4 x5 x6) }
someWords8 :: Int -> Gen [Word8]
someWords8 i = replicateM i (fromIntegral `fmap` (choose (0,255) :: Gen Int))
someWords16 :: Int -> Gen [Word16]
someWords16 i = replicateM i (fromIntegral `fmap` (choose (0,65535) :: Gen Int))
instance Arbitrary Version where
arbitrary = elements [ SSL2, SSL3, TLS10, TLS11, TLS12 ]
instance Arbitrary ProtocolType where
arbitrary = elements
[ ProtocolType_ChangeCipherSpec
, ProtocolType_Alert
, ProtocolType_Handshake
, ProtocolType_AppData ]
instance Arbitrary Word8 where
arbitrary = fromIntegral `fmap` (choose (0,255) :: Gen Int)
instance Arbitrary Word16 where
arbitrary = fromIntegral `fmap` (choose (0,65535) :: Gen Int)
instance Arbitrary Header where
arbitrary = do
pt <- arbitrary
ver <- arbitrary
len <- arbitrary
return $ Header pt ver len
instance Arbitrary ClientRandom where
arbitrary = ClientRandom `fmap` someWords8 32
instance Arbitrary ServerRandom where
arbitrary = ServerRandom `fmap` someWords8 32
instance Arbitrary Session where
arbitrary = do
i <- choose (1,2) :: Gen Int
case i of
1 -> return $ Session Nothing
2 -> (Session . Just) `fmap` someWords8 32
arbitraryCiphersIDs :: Gen [Word16]
arbitraryCiphersIDs = choose (0,200) >>= someWords16
arbitraryCompressionIDs :: Gen [Word8]
arbitraryCompressionIDs = choose (0,200) >>= someWords8
instance Arbitrary Handshake where
arbitrary = oneof
[ liftM6 ClientHello arbitrary arbitrary arbitrary arbitraryCiphersIDs arbitraryCompressionIDs (return Nothing)
, liftM6 ServerHello arbitrary arbitrary arbitrary arbitrary arbitrary (return Nothing)
, return HelloRequest
, return ServerHelloDone
]
{- quickcheck property -}
prop_header_marshalling_id x = (decodeHeader $ encodeHeader x) == Right x
prop_handshake_marshalling_id x = (decodeHs $ encodeHandshake x) == Right x
where
decodeHs b = either (Left . id) (\(ty, bdata) -> decodeHandshake TLS10 ty bdata) $ decodeHandshakeHeader b
{- main -}
options = TestOptions
{ no_of_tests = 2000
, length_of_tests = 1
, debug_tests = False }
main = do
runTests "marshalling=id" options
[ run prop_header_marshalling_id
, run prop_handshake_marshalling_id
]

71
tls.cabal Normal file
View file

@ -0,0 +1,71 @@
Name: tls
Version: 0.1
Description:
Implementation of the TLS protocol, focusing on purity and more type-checking.
.
Currently implement only partially the TLS1.0 protocol. Not yet properly secure.
Do not yet use as replacement to more mature implementation.
License: BSD3
License-file: LICENSE
Copyright: Vincent Hanquez <vincent@snarc.org>
Author: Vincent Hanquez <vincent@snarc.org>
Maintainer: Vincent Hanquez <vincent@snarc.org>
Synopsis: TLS protocol for Server and Client sides
Build-Type: Simple
Category: Network
stability: experimental
Cabal-Version: >=1.6
data-files: README, TODO
Flag test
Description: Build unit test
Default: False
Flag executable
Description: Build the executable
Default: False
Library
Build-Depends: base >= 3 && < 5,
mtl,
cryptohash,
binary >= 0.5,
bytestring,
vector,
AES, RSA, spoon,
cryptocipher,
certificate >= 0.2
Exposed-modules: Network.TLS.Client
Network.TLS.Server
Network.TLS.Struct
other-modules: Network.TLS.Cipher
Network.TLS.Compression
Network.TLS.Crypto
Network.TLS.MAC
Network.TLS.Packet
Network.TLS.State
Network.TLS.Sending
Network.TLS.Receiving
Network.TLS.SRandom
Network.TLS.Wire
ghc-options: -Wall
Executable stunnel
Main-is: Stunnel.hs
if flag(executable)
Build-Depends: network, haskell98, RSA
Buildable: True
else
Buildable: False
executable Tests
Main-is: Tests.hs
if flag(test)
Buildable: True
Build-Depends: base >= 3 && < 5, HUnit, QuickCheck, bytestring, haskell98
else
Buildable: False
source-repository head
type: git
location: git://github.com/vincenthz/hs-tls