Uso adecuado de la API HsOpenSSL para implementar un servidor TLS

141

Estoy tratando de descubrir cómo usar correctamente la API OpenSSL.Session en un contexto concurrente

Por ejemplo, suponga que quiero implementar un stunnel-style ssl-wrapper, esperaría tener la siguiente estructura básica de esqueleto, que implementa un ingenuofull-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

¿Cómo transformo el esqueleto anterior en un full-duplex ssl-wrapping tcp-forwarding proxy? ¿Dónde están los peligros WRT para la ejecución concurrente / paralela (en el contexto del caso de uso anterior) de las llamadas a funciones proporcionadas por la API HsOpenSSL?

PD: Todavía estoy luchando por comprender por completo cómo hacer que el código sea robusto para excepciones y fugas de recursos. Entonces, aunque no sea el foco principal de esta pregunta, si nota algo malo en el código anterior, deje un comentario.

hvr
fuente
11
Creo que esta podría ser una pregunta demasiado amplia para SO.
Don Stewart
1
Te
responderé
2
el enlace al documento está roto, aquí está el que está trabajando: hackage.haskell.org/packages/archive/HsOpenSSL/0.10.2/doc/html/…
Pascal Qyy
44
Hice algo similar ( full-duplex ssl-rewrapping tcp-forwarding), pero en su lugar usó Network.TLS(paquete tls). Y fue feo. Puede encontrarlo aquí , si está interesado.

Respuestas:

7

Para hacer esto, debe reemplazarlo copySocketcon dos funciones diferentes, una para manejar los datos del socket simple a SSL y la otra de SSL al socket simple:

  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

Luego debe modificar connectToServerpara que establezca una conexión 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

y cambiar finalizepara cerrar la sesión 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

Finalmente, no olvides ejecutar tus cosas principales dentro withOpenSSLcomo en

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
fuente
Esto ya ayuda mucho; esto proporciona un proxy local-no-ssl-to-remote-ssl correspondiente al stunnelsmodo cliente, ¿podría también proporcionar un ejemplo de cómo escuchar un socket ssl local (por ejemplo, para proporcionar un local-ssl-to-remote-non- proxy SSL)?
hvr