Utilisation appropriée de l'API HsOpenSSL pour implémenter un serveur TLS

141

J'essaie de comprendre comment utiliser correctement l' API OpenSSL.Session dans un contexte concurrent

Par exemple, supposons que je veuille implémenter un stunnel-style ssl-wrapper, je m'attendrais à avoir la structure de squelette de base suivante, qui implémente un naïffull-duplex tcp-port-forwarder:

runProxy :: PortID -> AddrInfo -> IO ()
runProxy localPort@(PortNumber lpn) serverAddrInfo = do
  listener <- listenOn localPort

  forever $ do
    (sClient, clientAddr) <- accept listener

    let finalize sServer = do
            sClose sServer
            sClose sClient

    forkIO $ do
        tidToServer <- myThreadId
        bracket (connectToServer serverAddrInfo) finalize $ \sServer -> do
            -- execute one 'copySocket' thread for each data direction
            -- and make sure that if one direction dies, the other gets
            -- pulled down as well
            bracket (forkIO (copySocket sServer sClient
                             `finally` killThread tidToServer))
                    (killThread) $ \_ -> do
                copySocket sClient sServer -- "controlling" thread

 where
  -- |Copy data from source to dest until EOF occurs on source
  -- Copying may also be aborted due to exceptions
  copySocket :: Socket -> Socket -> IO ()
  copySocket src dst = go
   where
    go = do
        buf <- B.recv src 4096
        unless (B.null buf) $ do
            B.sendAll dst buf
            go

  -- |Create connection to given AddrInfo target and return socket
  connectToServer saddr = do
    sServer <- socket (addrFamily saddr) Stream defaultProtocol
    connect sServer (addrAddress saddr)
    return sServer

Comment puis-je transformer le squelette ci-dessus en un full-duplex ssl-wrapping tcp-forwarding proxy? Quels sont les dangers WRT pour l'exécution simultanée / parallèle (dans le contexte du cas d'utilisation ci-dessus) des appels de fonction fournis par l'API HsOpenSSL?

PS: J'ai encore du mal à comprendre comment rendre le code robuste face aux exceptions et aux fuites de ressources. Donc, bien que ce ne soit pas l'objet principal de cette question, si vous remarquez quelque chose de mauvais dans le code ci-dessus, veuillez laisser un commentaire.

hvr
la source
11
Je pense que c'est peut-être une question trop large pour SO.
Don Stewart
1
Je vous
répondrai
2
le lien vers la doc est cassé, voici celui qui travaille: hackage.haskell.org/packages/archive/HsOpenSSL/0.10.2/doc/html/…
Pascal Qyy
4
J'ai fait quelque chose de similaire ( full-duplex ssl-rewrapping tcp-forwarding), mais il a utilisé Network.TLS(package tls) à la place. Et c'était moche. Vous pouvez le trouver ici , le cas échéant.

Réponses:

7

Pour ce faire, vous devez remplacer copySocketpar deux fonctions différentes, l'une pour gérer les données du plain socket vers SSL et l'autre de SSL vers le plain socket:

  copyIn :: SSL.SSL -> Socket -> IO ()
  copyIn src dst = go
   where
    go = do
        buf <- SSL.read src 4096
        unless (B.null buf) $ do
            SB.sendAll dst buf
            go

  copyOut :: Socket -> SSL.SSL -> IO ()
  copyOut src dst = go
   where
    go = do
        buf <- SB.recv src 4096
        unless (B.null buf) $ do
            SSL.write dst buf
            go

Ensuite, vous devez modifier connectToServerpour qu'il établisse une connexion SSL

  -- |Create connection to given AddrInfo target and return socket
  connectToServer saddr = do
    sServer <- socket (addrFamily saddr) Stream defaultProtocol
    putStrLn "connecting"
    connect sServer (addrAddress saddr)
    putStrLn "establishing ssl context"
    ctx <- SSL.context
    putStrLn "setting ciphers"
    SSL.contextSetCiphers ctx "DEFAULT"
    putStrLn "setting verfication mode"
    SSL.contextSetVerificationMode ctx SSL.VerifyNone
    putStrLn "making ssl connection"
    sslServer <- SSL.connection ctx sServer
    putStrLn "doing handshake"
    SSL.connect sslServer
    putStrLn "connected"
    return sslServer

et changer finalizepour fermer la session SSL

let finalize sServer = do
        putStrLn "shutting down ssl"
        SSL.shutdown sServer SSL.Unidirectional
        putStrLn "closing server socket"
        maybe (return ()) sClose (SSL.sslSocket sServer)
        putStrLn "closing client socket"
        sClose sClient

Enfin, n'oubliez pas d'exécuter vos éléments principaux à l'intérieur withOpenSSLcomme dans

main = withOpenSSL $ do
    let hints = defaultHints { addrSocketType = Stream, addrFamily = AF_INET }
    addrs <- getAddrInfo (Just hints) (Just "localhost") (Just "22222")
    let addr = head addrs
    print addr
    runProxy (PortNumber 11111) addr
Geoff Reedy
la source
Cela aide déjà beaucoup; cela fournit un proxy local-non-ssl-to-remote-ssl correspondant au stunnelsmode client, pourriez-vous également fournir un exemple sur la façon d'écouter un socket local ssl (par exemple pour fournir un local-ssl-to-remote-non- proxy SSL)?
hvr