【问题标题】:How do I add TLS encryption to this TCP server?如何向此 TCP 服务器添加 TLS 加密?
【发布时间】:2015-01-09 00:32:35
【问题描述】:

我已经为一个项目构建了一个 TCP 服务器,它工作正常,但是是时候添加 TLS 了。我试过Network.Simple.TCP.TLS,但是当我把它集成到我更大的项目中时,我遇到了一些我无法解决的依赖冲突。

这里的代码是简化的测试服务器,我稍后将集成到更大的项目中。我希望这不会隐藏任何重要的细节。

这是一个没有 TLS 的工作 echo 服务器:

import Control.Concurrent
import Network.Socket
import System.IO

main :: IO ()
main = do
  let port = 4653
  sock <- socket AF_INET Stream 0
  setSocketOption sock ReuseAddr 1
  bindSocket sock (SockAddrInet (fromInteger port) iNADDR_ANY)
  putStrLn $ "Listening on port " ++ show port
  listen sock 2
  mainLoop sock


mainLoop :: Socket -> IO ()
mainLoop sock = do
  conn <- accept sock
  _ <- forkIO $ runConn conn
  mainLoop sock


runConn :: (Socket, SockAddr) -> IO ()
runConn (sock, _) = do
  hdl <- socketToHandle sock ReadWriteMode
  hSetBuffering hdl NoBuffering

  request <- hGetLine hdl 
  hPrint hdl request

  hClose hdl

这是我对 TLS 集成的尝试:

{-# LANGUAGE OverloadedStrings #-}

import Control.Concurrent
import qualified Crypto.Random.AESCtr       as AESCtr
import qualified Data.ByteString.Char8      as BSC
import qualified Data.ByteString.Lazy.Char8 as LBS
import Data.Default.Class (def)
import Network.Socket
import qualified Network.TLS                as T
import qualified Network.TLS.Extra          as TE
import System.IO

main :: IO ()
main = do
  let port = 4653

  cred <- credentials
  let creds = case cred of
                Right c -> T.Credentials [c]
                Left e -> error e

  sock <- socket AF_INET Stream 0
  setSocketOption sock ReuseAddr 1
  bindSocket sock (SockAddrInet (fromInteger port) iNADDR_ANY)
  putStrLn $ "Listening on port " ++ show port
  listen sock 2
  mainLoop sock creds


mainLoop :: Socket -> T.Credentials -> IO ()
mainLoop sock creds = do
  conn <- accept sock
  _ <- forkIO $ runConn creds conn
  mainLoop sock creds


runConn :: T.Credentials -> (Socket, SockAddr) -> IO ()
runConn creds (sock, _) = do
  hdl <- socketToHandle sock ReadWriteMode
  hSetBuffering hdl NoBuffering

  ctx <- context creds hdl
  T.handshake ctx

  request <- T.recvData ctx
  T.sendData ctx (LBS.fromChunks [request])

  hClose hdl


context :: T.Credentials -> Handle -> IO T.Context
context creds hdl = T.contextNew (sockBackend hdl) (sockParams creds) =<< AESCtr.makeSystem


credentials :: IO (Either String T.Credential)
credentials = T.credentialLoadX509 "cert/server.crt" "cert/server.key"

sockBackend :: Handle -> T.Backend
sockBackend hdl = T.Backend { T.backendFlush = hFlush hdl
                            , T.backendClose = hClose hdl
                            , T.backendSend  = hPrint hdl
                            , T.backendRecv  = hRecv hdl BSC.empty
                            }


hRecv :: Handle -> BSC.ByteString -> Int -> IO BSC.ByteString
hRecv _ ack 0 = return ack
hRecv hdl ack n = do
  c <- hGetChar hdl
  hRecv hdl (ack `BSC.append` BSC.pack [c]) (n - 1)


sockParams :: T.Credentials -> T.ServerParams
sockParams creds = def { T.serverWantClientCert = False
                       , T.serverShared         = shared creds
                       , T.serverSupported      = supported
                       }

shared :: T.Credentials -> T.Shared
shared creds = def { T.sharedCredentials    = creds
                   }


supported :: T.Supported
supported = def { T.supportedVersions = [T.TLS10, T.TLS11, T.TLS12]
                , T.supportedCiphers  = ciphers
                }

ciphers :: [T.Cipher]
ciphers =
    [ TE.cipher_AES128_SHA1
    , TE.cipher_AES256_SHA1
    , TE.cipher_RC4_128_MD5
    , TE.cipher_RC4_128_SHA1
    ]

它编译并运行,但每当我尝试点击它时,它就会打印这个错误:

Main.hs: ConnectionNotEstablished

在这里,我感觉有点不知所措。任何人都可以指出问题或向此服务器添加 TLS 加密的更好方法吗?

编辑:我发现了部分问题。我错过了T.handshake 电话。现在我无法从客户端连接。如果我能顺利运行,我会再次更新。

EDIT2:顺便说一下,新的错误是:

HandshakeFailed (Error_Packet_Parsing "Failed reading: invalid header type: 34\nFrom:\theader\n\n")

我在网络上的几个地方发现了这个错误,但我还没有看到附加的答案。

【问题讨论】:

  • @ThomasM.DuBuisson 谢谢,但链接给了我一个 404。回购是私有的还是移动的?
  • 哦,呵呵。估计他还没发没关系!

标签: sockets haskell ssl encryption tcp


【解决方案1】:

正如我在第一次编辑我的问题时提到的,我最初错过了 handshake 电话。这还不足以解决这个问题。最终,我发现我可以将sock 作为backend 传递,而无需手动创建Backend 的实例。当我切换到该方法时,它开始使用下面包含的 Python 客户端。

Haskell 服务器:

{-# LANGUAGE OverloadedStrings #-}

import Control.Applicative
import Control.Concurrent
import Control.Monad (void)
import qualified Crypto.Random.AESCtr       as AESCtr
import qualified Data.ByteString.Char8      as BSC 
import qualified Data.ByteString.Lazy.Char8 as LBS 
import Data.Default.Class (def)
import Network.Socket
import qualified Network.TLS                as T
import qualified Network.TLS.Extra          as TE

main :: IO ()
main = do
  let port = 4653

  cred <- credentials
  let creds = case cred of
                Right c -> T.Credentials [c] 
                Left e -> error e

  sock <- socket AF_INET Stream 0
  setSocketOption sock ReuseAddr 1
  bindSocket sock (SockAddrInet (fromInteger port) iNADDR_ANY)
  putStrLn $ "Listening on port " ++ show port
  listen sock 2
  mainLoop sock creds


mainLoop :: Socket -> T.Credentials -> IO ()
mainLoop sock creds = do
  conn <- accept sock
  _ <- forkIO $ runConn creds conn
  mainLoop sock creds


runConn :: T.Credentials -> (Socket, SockAddr) -> IO ()
runConn creds (sock, _) = do

  ctx <- T.contextNew sock (sockParams creds) =<< AESCtr.makeSystem
  T.handshake ctx 

  request <- T.recvData ctx
  print request
  T.sendData ctx (LBS.fromChunks [request])

  T.contextClose ctx


credentials :: IO (Either String T.Credential)
credentials = T.credentialLoadX509 "cert/server.crt" "cert/server.key"

sockParams :: T.Credentials -> T.ServerParams
sockParams creds = def { T.serverWantClientCert = False
                       , T.serverShared         = shared creds
                       , T.serverSupported      = supported
                       }

shared :: T.Credentials -> T.Shared
shared creds = def { T.sharedCredentials    = creds
                   }


supported :: T.Supported
supported = def { T.supportedVersions = [T.TLS10]
                , T.supportedCiphers = ciphers
                }


ciphers :: [T.Cipher]
ciphers =
    [ TE.cipher_AES128_SHA1
    , TE.cipher_AES256_SHA1
    , TE.cipher_RC4_128_MD5
    , TE.cipher_RC4_128_SHA1
    ]

Python 客户端:

# Echo client program
import socket
import json
import ssl 

def recv_all(s):
    buf = 4096
    data = ''
    chunk = s.recv(buf)
    while len(chunk) > 0:
        data = data + chunk
        chunk = s.recv(buf)
    return data

def main():
    HOST = '127.0.0.1'    # The remote host
    PORT = 4653              # The same port as used by the server
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    ss = ssl.wrap_socket(s, ssl_version=ssl.PROTOCOL_TLSv1, do_handshake_on_connect=False)
    ss.connect((HOST, PORT))
    ss.do_handshake()
    ss.sendall('Hello, world\r\n')

    data = recv_all(ss)

    s.close()
    print("Received %s" % data)


if __name__ == "__main__":
    main()

编辑:根据我下面的 cmets,上面的服务器会切断大于 16k 的输入。我目前的解决方案是使用下面的recvAll 方法来替代上面的T.recvData

recvAll :: T.Context -> IO BSC.ByteString 
recvAll ctx = go BSC.empty
  where go acc = do
          pkt <- T.recvData ctx 
          print $ BSC.length pkt 
          if BSC.length pkt == 16384
            then go $ acc <> pkt 
              else return $ acc <> pkt

这个功能有一些真正的缺点!最值得注意的是,如果您实际上有一个正好是 16k(或其任何倍数)的数据包,它将永远旋转!

【讨论】:

  • 注意:服务器在16k时中断。
  • Network.TLS 中的 recvData 函数一次接收 16k。有关累积整个方法的函数,请参见上面的编辑。一旦这个有实际问题,我会尽快用更好的方法更新......
猜你喜欢
  • 2022-08-23
  • 2011-07-13
  • 1970-01-01
  • 2013-09-12
  • 1970-01-01
  • 2013-06-28
  • 2019-01-16
  • 1970-01-01
  • 2018-03-14
相关资源
最近更新 更多