Правильное использование API HsOpenSSL для реализации TLS-сервера

141

Я пытаюсь понять, как правильно использовать API OpenSSL.Session в параллельном контексте.

Например, предположим, что я хочу реализовать a 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

Как мне превратить вышеуказанный скелет в full-duplex ssl-wrapping tcp-forwarding proxy? В чем опасность WRT для одновременного / параллельного выполнения (в контексте приведенного выше варианта использования) вызовов функций, предоставляемых API HsOpenSSL?

PS: Я все еще пытаюсь полностью понять, как сделать код устойчивым к исключениям и утечкам ресурсов. Так что, хотя этот вопрос не является основным, но если вы заметили что-то плохое в приведенном выше коде, оставьте комментарий.

hvr
источник
11
Я думаю, что это слишком широкий вопрос для SO.
Дон Стюарт
1
Я вернусь к вам по этому
поводу
2
ссылка на документ не работает, вот тот, кто работает: hackage.haskell.org/packages/archive/HsOpenSSL/0.10.2/doc/html/…
Pascal Qyy
4
Я сделал что-то похожее ( full-duplex ssl-rewrapping tcp-forwarding), но вместо него использовал Network.TLS(package tls). И это было некрасиво. Вы можете найти его здесь , если вам это интересно.

Ответы:

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

Затем вам нужно изменить 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

и измените, 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

Наконец, не забудьте запустить свои основные вещи внутри, 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
Джефф Риди
источник
Это уже очень помогает; это обеспечивает прокси-сервер local-non-ssl-to-remote-ssl, соответствующий stunnelsклиентскому режиму, не могли бы вы также привести пример того, как прослушивать локальный сокет ssl (например, чтобы предоставить local-ssl-to-remote-non- ssl прокси)?
hvr