natskell-0.0.0.1: internal/Plumbing/Network/Connection/Tls.hs
{-# LANGUAGE TypeApplications #-}
module Network.Connection.Tls
( configureTransport
, upgradeTcp
) where
import Control.Exception
import Control.Monad
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as BC
import qualified Data.ByteString.Lazy as LBS
import Network.Connection.Core
( bufferRead
, currentTransport
, enableReadWorker
, pointTransport
)
import Network.Connection.Types
( Conn
, Transport (..)
, TransportOption (..)
)
import qualified Network.Socket as NS
import qualified Network.Socket.ByteString as NSB
import qualified Network.TLS as TLS
tlsTransport :: TLS.Context -> Transport
tlsTransport ctx =
Transport
{ transportRead = const (TLS.recvData ctx)
, transportWrite = TLS.sendData ctx . LBS.fromStrict
, transportWriteLazy = TLS.sendData ctx
, transportFlush = TLS.contextFlush ctx
, transportClose = do
void $ try @SomeException (TLS.bye ctx)
void $ try @SomeException (TLS.contextClose ctx)
, transportUpgrade = Nothing
}
upgradeTcp :: NS.Socket -> TLS.ClientParams -> IO (Either String Transport)
upgradeTcp sock params = do
let backend = TLS.Backend
{ TLS.backendSend = NSB.sendAll sock
, TLS.backendRecv = NSB.recv sock
, TLS.backendFlush = pure ()
, TLS.backendClose = NS.close sock
}
result <- try @SomeException $ do
ctx <- TLS.contextNew backend params
TLS.handshake ctx
pure (tlsTransport ctx)
case result of
Left err -> return $ Left (show err)
Right transport -> return $ Right transport
upgradeToTLS :: Conn -> TLS.ClientParams -> IO (Either String ())
upgradeToTLS conn params = do
current <- currentTransport conn
case current of
Nothing -> return $ Left "Transport not initialized"
Just currentTransport' ->
case transportUpgrade currentTransport' of
Nothing -> return $ Right ()
Just upgrade -> do
result <- upgrade params
case result of
Left err -> return $ Left err
Right newTransport -> do
pointTransport conn newTransport
return $ Right ()
upgradeToTLSWithConfig :: Conn -> String -> Maybe (ByteString, ByteString) -> IO (Either String ())
upgradeToTLSWithConfig conn host tlsConfig = do
paramsResult <- buildTlsParams host tlsConfig
case paramsResult of
Left err -> return (Left err)
Right params -> upgradeToTLS conn params
configureTransport :: Conn -> TransportOption -> IO (Either String ())
configureTransport conn transportOption = do
let useTls = transportTlsRequested transportOption || transportTlsRequired transportOption
if useTls
then do
result <- upgradeToTLSWithConfig conn (transportHost transportOption) (transportTlsCert transportOption)
case result of
Left err -> return (Left err)
Right () -> do
enableReadWorker conn
return (Right ())
else do
bufferRead conn (transportInitialBytes transportOption)
enableReadWorker conn
return (Right ())
buildTlsParams :: String -> Maybe (ByteString, ByteString) -> IO (Either String TLS.ClientParams)
buildTlsParams host tlsConfig = do
let base = TLS.defaultParamsClient host (BC.pack host)
hooks = TLS.clientHooks base
shared = TLS.clientShared base
acceptAny = hooks { TLS.onServerCertificate = \_ _ _ _ -> return [] }
case tlsConfig of
Just (certPem, keyPem) ->
case TLS.credentialLoadX509FromMemory certPem keyPem of
Left err -> return (Left err)
Right cred ->
let hooks' = acceptAny { TLS.onCertificateRequest = \_ -> return (Just cred) }
shared' = shared { TLS.sharedCredentials = TLS.Credentials [cred] }
in return (Right base { TLS.clientHooks = hooks', TLS.clientShared = shared' })
Nothing -> return (Right base { TLS.clientHooks = acceptAny })