2010-11-28 11:37:36 +00:00
|
|
|
{-# LANGUAGE DeriveDataTypeable #-}
|
2010-09-09 21:47:19 +00:00
|
|
|
import Network
|
|
|
|
import System.IO
|
2010-11-28 11:37:36 +00:00
|
|
|
import System.Console.CmdArgs
|
2010-09-19 09:50:37 +00:00
|
|
|
|
2010-09-09 21:47:19 +00:00
|
|
|
import qualified Data.ByteString as B
|
|
|
|
import qualified Data.ByteString.Lazy as L
|
|
|
|
import qualified Data.ByteString.Lazy.Char8 as LC
|
|
|
|
|
2010-09-19 09:49:42 +00:00
|
|
|
import Control.Applicative ((<$>))
|
2010-09-19 09:50:37 +00:00
|
|
|
import Control.Concurrent (forkIO)
|
2010-09-09 21:47:19 +00:00
|
|
|
import Control.Exception (bracket)
|
2010-09-19 09:50:37 +00:00
|
|
|
import Control.Monad (forM_, when, replicateM)
|
|
|
|
import Control.Monad.Trans (lift)
|
|
|
|
|
2010-09-09 21:47:19 +00:00
|
|
|
import Data.Word
|
|
|
|
import Data.Bits
|
|
|
|
import Data.Maybe
|
2010-09-19 09:50:37 +00:00
|
|
|
|
2010-09-09 21:47:19 +00:00
|
|
|
import Data.Certificate.PEM
|
|
|
|
import Data.Certificate.X509
|
|
|
|
import Data.Certificate.Key
|
|
|
|
|
2010-09-19 09:50:37 +00:00
|
|
|
import Network.TLS.Cipher
|
|
|
|
import Network.TLS.SRandom
|
|
|
|
import Network.TLS.Struct
|
|
|
|
import Network.TLS.MAC
|
|
|
|
|
|
|
|
import qualified Network.TLS.Client as C
|
|
|
|
import qualified Network.TLS.Server as S
|
|
|
|
|
2010-09-09 21:47:19 +00:00
|
|
|
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
|
|
|
|
|
2010-10-03 10:23:12 +00:00
|
|
|
tlsclient handle = do
|
2010-11-28 10:30:05 +00:00
|
|
|
C.initiate handle
|
2010-09-09 21:47:19 +00:00
|
|
|
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 ()
|
|
|
|
|
2010-11-04 19:05:36 +00:00
|
|
|
getRandomGen :: IO SRandomGen
|
|
|
|
getRandomGen = makeSRandomGen >>= either (fail . show) (return . id)
|
|
|
|
|
2010-10-03 10:23:12 +00:00
|
|
|
tlsserver handle = do
|
|
|
|
S.listen handle
|
2010-09-09 21:47:19 +00:00
|
|
|
_ <- 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
|
2010-11-04 19:05:36 +00:00
|
|
|
rng <- getRandomGen
|
2010-09-09 21:47:19 +00:00
|
|
|
|
|
|
|
let serverstate = S.TLSServerParams
|
2010-09-26 14:02:59 +00:00
|
|
|
{ S.spAllowedVersions = [TLS10,TLS11]
|
2010-09-09 21:47:19 +00:00
|
|
|
, S.spSessions = []
|
|
|
|
, S.spCiphers = ciphers
|
|
|
|
, S.spCertificate = Just (certdata, cert, pk)
|
|
|
|
, S.spWantClientCert = False
|
2010-09-20 07:45:41 +00:00
|
|
|
, S.spCallbacks = S.TLSServerCallbacks
|
|
|
|
{ S.cbCertificates = Nothing }
|
2010-09-09 21:47:19 +00:00
|
|
|
}
|
|
|
|
|
2010-10-03 10:23:12 +00:00
|
|
|
S.runTLSServer (tlsserver handle) serverstate rng
|
2010-09-09 21:47:19 +00:00
|
|
|
putStrLn "end"
|
|
|
|
|
2010-11-28 11:37:36 +00:00
|
|
|
mainServerAccept cert socket = do
|
2010-09-09 21:47:19 +00:00
|
|
|
(h, d, _) <- accept socket
|
|
|
|
forkIO $ clientProcess cert (h, d)
|
2010-11-28 11:37:36 +00:00
|
|
|
mainServerAccept cert socket
|
2010-09-09 21:47:19 +00:00
|
|
|
|
2010-09-26 09:34:47 +00:00
|
|
|
readCertificate :: FilePath -> IO (B.ByteString, Certificate)
|
2010-09-09 21:47:19 +00:00
|
|
|
readCertificate filepath = do
|
|
|
|
content <- B.readFile filepath
|
|
|
|
let certdata = case parsePEMCert content of
|
2010-10-03 09:32:37 +00:00
|
|
|
Nothing -> error ("no valid certificate section")
|
|
|
|
Just x -> x
|
2010-09-26 09:34:47 +00:00
|
|
|
let cert = case decodeCertificate $ L.fromChunks [certdata] of
|
2010-09-09 21:47:19 +00:00
|
|
|
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
|
2010-10-03 09:32:37 +00:00
|
|
|
let pkdata = case parsePEMKeyRSA content of
|
|
|
|
Nothing -> error ("no valid RSA key section")
|
|
|
|
Just x -> L.fromChunks [x]
|
2010-09-09 21:47:19 +00:00
|
|
|
let pk = case decodePrivateKey pkdata of
|
|
|
|
Left err -> error ("cannot decode key: " ++ err)
|
|
|
|
Right x -> x
|
|
|
|
return (pkdata, pk)
|
|
|
|
|
2010-11-28 11:37:36 +00:00
|
|
|
data Stunnel =
|
2010-11-28 11:50:55 +00:00
|
|
|
Client { srcPort :: Int, destinationPort :: Int, destination :: String, sourceType :: String, source :: String }
|
2010-11-28 11:37:36 +00:00
|
|
|
| Server { srcPort :: Int, destinationPort :: Int, destination :: String, certificate :: FilePath, key :: FilePath }
|
|
|
|
deriving (Show, Data, Typeable)
|
|
|
|
|
|
|
|
clientOpts = Client
|
|
|
|
{ srcPort = 6060 &= help "port to listen on" &= typ "PORT"
|
|
|
|
, destinationPort = 6061 &= help "port to connect to" &= typ "PORT"
|
|
|
|
, destination = "localhost" &= help "address to connect to" &= typ "ADDRESS"
|
2010-11-28 11:50:55 +00:00
|
|
|
, sourceType = "tcp" &= help "type of source (tcp, unix, fd)" &= typ "SOURCETYPE"
|
|
|
|
, source = "" &= help "source address influenced by source type" &= typ "ADDRESS"
|
2010-11-28 11:37:36 +00:00
|
|
|
}
|
|
|
|
&= help "connect to a remote destination that use SSL/TLS"
|
|
|
|
|
|
|
|
serverOpts = Server
|
|
|
|
{ srcPort = 6061 &= help "port to listen on" &= typ "PORT"
|
|
|
|
, destinationPort = 6060 &= help "port to connect to" &= typ "PORT"
|
|
|
|
, destination = "localhost" &= help "address to connect to" &= typ "ADDRESS"
|
|
|
|
, certificate = "certificate.pem" &= help "X509 public certificate to use" &= typ "FILE"
|
|
|
|
, key = "certificate.key" &= help "private key linked to the certificate" &= typ "FILE"
|
|
|
|
}
|
|
|
|
&= help "listen for connection that use SSL/TLS and relay it to a different connection"
|
|
|
|
|
|
|
|
mode = cmdArgsMode $ modes [clientOpts,serverOpts]
|
|
|
|
&= help "create SSL/TLS tunnel in client or server mode" &= program "stunnel" &= summary "Stunnel v0.1 (Haskell TLS)"
|
|
|
|
|
|
|
|
doClient :: Stunnel -> IO ()
|
|
|
|
doClient args = do
|
|
|
|
let host = destination args
|
|
|
|
let port = PortNumber $ fromIntegral $ destinationPort args
|
|
|
|
|
|
|
|
rng <- getRandomGen
|
|
|
|
|
|
|
|
handle <- connectTo host port
|
|
|
|
hSetBuffering handle NoBuffering
|
|
|
|
|
|
|
|
let clientstate = C.TLSClientParams
|
|
|
|
{ C.cpConnectVersion = TLS10
|
|
|
|
, C.cpAllowedVersions = [ TLS10, TLS11 ]
|
|
|
|
, C.cpSession = Nothing
|
|
|
|
, C.cpCiphers = ciphers
|
|
|
|
, C.cpCertificate = Nothing
|
|
|
|
, C.cpCallbacks = C.TLSClientCallbacks
|
|
|
|
{ C.cbCertificates = Nothing
|
|
|
|
}
|
|
|
|
}
|
|
|
|
C.runTLSClient (tlsclient handle) clientstate rng
|
|
|
|
|
|
|
|
putStrLn "end"
|
|
|
|
|
|
|
|
doServer :: Stunnel -> IO ()
|
|
|
|
doServer args = do
|
|
|
|
let port = PortNumber $ fromIntegral $ srcPort args
|
|
|
|
cert <- readCertificate $ certificate args
|
|
|
|
pk <- readPrivateKey $ key args
|
|
|
|
bracket
|
|
|
|
(listenOn port)
|
|
|
|
sClose
|
|
|
|
(mainServerAccept (cert, snd pk))
|
|
|
|
|
2010-09-09 21:47:19 +00:00
|
|
|
main = do
|
2010-11-28 11:37:36 +00:00
|
|
|
args <- cmdArgsRun mode
|
|
|
|
case args of
|
2010-11-28 11:50:55 +00:00
|
|
|
Client _ _ _ _ _ -> doClient args
|
2010-11-28 11:37:36 +00:00
|
|
|
Server _ _ _ _ _ -> doServer args
|