正确使用HsOpenSSL API来实现TLS服务器

hvr*_*hvr 142 ssl haskell openssl

我试图弄清楚如何在并发上下文中正确使用OpenSSL.Session API

例如假设我想实现一个stunnel-style ssl-wrapper,我希望有以下基本的骨架结构,它实现了一个天真的full-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
Run Code Online (Sandbox Code Playgroud)

如何将上述骨架转换为full-duplex ssl-wrapping tcp-forwarding proxy?对于HsOpenSSL API提供的函数调用的并发/并行执行(在上述用例的上下文中)的WRT危险在哪里?

PS:我仍然在努力完全理解如何使代码在异常和资源泄漏方面具有强大的功能.所以,尽管不是这个问题的主要焦点,但如果您在上面的代码中发现了一些不好的内容,请发表评论.

Geo*_*edy 7

为此,您需要替换copySocket两个不同的函数,一个用于处理从普通套接字到SSL的数据,另一个用于从SSL到普通套接字:

  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
Run Code Online (Sandbox Code Playgroud)

然后,您需要进行修改,connectToServer以便建立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
Run Code Online (Sandbox Code Playgroud)

并更改finalize为关闭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
Run Code Online (Sandbox Code Playgroud)

最后,不要忘了中运行你的主要的东西withOpenSSL,如

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
Run Code Online (Sandbox Code Playgroud)