Uso adequado da API HsOpenSSL para implementar um servidor TLS

141

Estou tentando descobrir como usar corretamente o API OpenSSL.Session em um contexto simultâneo

Por exemplo, suponha que eu queira implementar um stunnel-style ssl-wrapper, eu esperaria ter a seguinte estrutura básica do esqueleto, que implementa uma ingênuafull-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

Como faço para transformar o esqueleto acima em um full-duplex ssl-wrapping tcp-forwarding proxy ? Onde estão os perigos do WRT para a execução simultânea / paralela (no contexto do caso de uso acima) das chamadas de função fornecidas pela API HsOpenSSL?

PS: Ainda estou lutando para compreender completamente como tornar o código robusto, com exceções e vazamentos de recursos. Portanto, apesar de não ser o foco principal desta pergunta, se você notar algo ruim no código acima, deixe um comentário.

hvr
fonte
11
Eu acho que isso pode ser uma pergunta muito ampla para SO.
Don Stewart
1
Eu vou voltar para você sobre isso :-)
Abhineet 28/06
2
o link para o doc está quebrado, aqui é o único que está trabalhando: hackage.haskell.org/packages/archive/HsOpenSSL/0.10.2/doc/html/...
Pascal Qyy
4
Eu fiz algo semelhante ( full-duplex ssl-rewrapping tcp-forwarding), mas ele usou Network.TLS(pacote tls). E foi feio. Você pode encontrá-lo aqui , se estiver interessado.

Respostas:

7

Para fazer isso, é necessário substituir copySocketpor duas funções diferentes, uma para manipular dados do soquete simples para SSL e a outra do SSL para o soquete simples:

  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

Então você precisa modificar connectToServerpara que ele estabeleça uma conexão 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

e mude finalizepara desligar a sessão 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

Por fim, não se esqueça de executar suas principais coisas withOpenSSLcomo dentro

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
fonte
Isso já ajuda muito; isso fornece um proxy local-não-ssl-para-ssl-remoto correspondendo ao stunnelsmodo cliente; você também pode fornecer um exemplo de como ouvir um soquete ssl local (por exemplo, para fornecer um local-ssl-para-remoto-não- proxy SSL)?
HVR