haskell amqp x509 客户端认证

haskell amqp x509 client auth

我一直在努力弄清楚如何通过 Network.AMQP 使用 x509 客户端身份验证。似乎我需要使用(除其他外) coTLSSettings 参数创建一个 AMQP.ConnectionOpts,如下所示:

import qualified Network.AMQP as AMQP
import Network.Connection
let opts = AMQP.ConnectionOpts {
    ..
  , coTLSSettings = Just $ AMQP.TLSCustom $ ...
}

在这一点上(省略号),阅读了一些 Network.Connection 文档(并且超出了我的理解范围),它开始看起来非常复杂。我想知道我是否走上了正确的道路。

所以,我的问题是:如何轻松实现 x509 客户端身份验证?如果答案是 "you can't," 有谁知道我在哪里可以找到使用 Network.Connection 模块的 x509 客户端身份验证示例?

我们需要做两件事(我的测试环境是三件)。

  1. 读取客户端凭据
  2. 设置密码套件为defaultParamsClient设置空密码套件(我不知道为什么。)。
  3. (对于我的测试环境)读取 CA 根证书,因此我们可以验证提供给我们的服务器证书。如果您不在测试环境中,则此证书应安装在系统证书存储中,并且应该是默认的。在这种情况下,您可以从程序中删除 CertificateStore 处理。

下面程序中的函数mkMyTLSSettings替换了defaultParamsClient结果中提到的部分。在用作 onCertificateRequest 的函数中,您可以使用参数并根据参数 values 分发不同的凭据。读入所需的值本身 main 摆脱 IO.

对于以下程序,我修改了在此 中找到的位。

{-# LANGUAGE OverloadedStrings #-}
module Main where

import Data.Default.Class
import Network.AMQP
import Network.Socket.Internal (PortNumber)
import Network.TLS
import Network.TLS.Extra.Cipher (ciphersuite_default)
import Data.X509.CertificateStore (CertificateStore (..), readCertificateStore)
import Data.Maybe
import qualified Data.ByteString as BS
import qualified Network.Connection as C
import qualified Data.ByteString.Lazy.Char8 as BL

mkMyTLSSettings :: CertificateStore -> Credential -> C.TLSSettings
mkMyTLSSettings castore creds =
  let defaultParams = defaultParamsClient "127.0.0.1" BS.empty
      newClientShared = (clientShared defaultParams) { sharedCAStore = castore }
      newClientSupported = (clientSupported defaultParams) { supportedCiphers = ciphersuite_default }
      newClientHooks = (clientHooks defaultParams) { onCertificateRequest = \_ -> return (Just creds) }
  in C.TLSSettings $ defaultParams { clientShared = newClientShared
                                   , clientSupported = newClientSupported
                                   , clientHooks = newClientHooks
                                   }

myTLSSettings :: CertificateStore -> Credential -> TLSSettings
myTLSSettings castore creds = TLSCustom $ mkMyTLSSettings castore creds

myTLSConnectionOpts :: TLSSettings -> ConnectionOpts
myTLSConnectionOpts opts = ConnectionOpts
  [("127.0.0.1", 5671 :: PortNumber)]
  "/"
  [plain "guest" "guest"]
  (Just 131072)
  Nothing
  (Just 1)
  (Just opts)

testConnectionOpts :: ConnectionOpts -> IO ()
testConnectionOpts opts = do 
   conn <- openConnection'' opts
   chan <- openChannel conn
   declareQueue chan newQueue {queueName = "hello"}
   putStrLn "Trying to register callback"
   consumeMsgs chan "hello" Ack myCallback
   publishMsg chan "" "hello" newMsg {msgBody = (BL.pack "hello world"), msgDeliveryMode = Just Persistent}
   getLine
   closeConnection conn
   putStrLn "connection closed"

main :: IO ()
main = do
  testConnectionOpts defaultConnectionOpts
  putStrLn "trying with tls"
  castore <- maybe (error "couldn't read CA root Certificate") id <$> (readCertificateStore "/pathto/rootCA.pem")
  creds <- either error id <$> credentialLoadX509 "/pathto/client.pem" "/pathto/client.key"
  let opts = myTLSSettings castore creds
  testConnectionOpts (myTLSConnectionOpts opts)

myCallback :: (Message, Envelope) -> IO ()
myCallback (msg, env) = do
  putStrLn $ "received message: " ++ (BL.unpack $ msgBody msg)
  ackEnv env

作为 gist.

我在这个程序中的第一次沟通是为了确保 rabbitmq 设置正确,我真的只遇到了 TLS 错误。如果删除第 20 和 23 行,您可以测试是否正确配置了 rabbitmq。在这种情况下,连接尝试应该会失败,因为我们没有提供客户端证书。

我创建了一个用于测试的玩具 CA,并颁发了一个用于 rabbitmq 服务器的证书和一个用于客户端的证书。所以我有一个文件 rootCA.pem,它存储了 CA 根证书和 rabbitmq.keyrabbitmq.pem 等文件,这些文件用于使用 rabbitmq 设置 TLS。也为客户提供 client.pemclient.key。我通过设置将 rabbitmq 配置为仅服务于提供可信赖证书的客户端 fail_if_no_peer_certtrue 并设置 {verify, verify_peer} 选项。

在我的第一次尝试中,我遇到了 LeafNotV3 的错误,这意味着我在第一次尝试时创建了 rabbitmq.pem 错误。它是一个 X509.v1 证书,默认情况下不被 Network.TLS 接受。我需要确保创建一个 X509.v3 证书,这是通过在颁发证书时启用某些扩展来完成的 rabbitmq.pem,请参阅 here。我需要将选项 -req 添加到此处引用的命令行以使其工作。