5

谁能解释我如何将 SSL 添加到我正在使用的 Python STOMP 客户端。我在 ActiveMQ 配置文件中添加了 stomp+ssl 传输连接器,我的基本 Python STOMP 客户端如下:

import time
import sys
import stomp
class MyListener(stomp.ConnectionListener):
    def on_error(self, headers, message):
        print('received an error "%s"' % message)
    def on_message(self, headers, message):
        print('received a message "%s"' % message)
conn = stomp.Connection()
conn.set_listener('', MyListener())
conn.start()
conn.connect('admin', 'password', wait=True)
conn.subscribe(destination='/queue/test', id=1, ack='auto')
conn.send(body=' '.join(sys.argv[1:]), destination='/queue/test')
time.sleep(2)
conn.disconnect()

我创建了http://activemq.apache.org/how-do-i-use-ssl.html文档中给出的密钥库和信任库,并将它们添加到SSL_OPTS代理中的环境变量中,但我找不到如何使用密钥库和信任库初始化 Python STOMP 客户端。我应该使用方法中给出的 SSL 参数stomp.Connection()吗?如果是,该怎么做?

谁能解释一下是否有其他方法可以通过 STOMP 添加 SSL?

4

3 回答 3

6

Python STOMP 客户端(从 4.1.20 版开始)使用 an来SSLContext处理其密钥对/证书,因此没有理由为客户端生成 Java KeyStore。

考虑到这一点,让我们完成设置 ApacheMQ 以支持 SSL 包装的 STOMP 连接的整个过程。以下过程已在 ApacheMQ 5.15.4 上进行了测试。我们通过在代理和客户端之间手动移动自签名证书来明确设置双向信任;使用证书颁发机构也是可能的,但如何做到这一点是一个不同的问题。

创建客户端证书

如上所述,在 Python 方面,KeyStore 几乎没有什么用处,并且由于SSLContext需要 PEM 编码的证书,我们不妨手动创建密钥对和证书(即使用openssl)。首先,在客户端机器上,让我们创建一个 4096 位的 RSA 密钥:

openssl genrsa -out client.key 4096

使用它,将公钥部分转换为证书并使用密钥本身对其进行签名;由于我们将手动将证书移动到代理,因此自签名证书不是问题:

openssl req -new -out client.csr -key client.key
openssl x509 -req -days 365 -in client.csr -signkey client.key -out client.pem
rm client.csr

STOMP 客户端将需要签名证书client.pem和私钥client.key,而代理只需要证书。

创建代理证书

在代理上,我们可以按照 Apache 指南的第一部分,使用 Javakeytool创建一个带有服务器密钥的 KeyStore:

keytool -genkeypair -alias broker -keyalg RSA -keysize 4096 -keystore broker.ks

当提示输入“名字和姓氏”时,提供服务器的主机名,在我们的示例中,我们将简单地设为localhost;如果代理和客户端在不同的服务器上运行,请确保将其设置为 Python 客户端最终用于识别代理的任何内容:

What is your first and last name?
  [Unknown]:  localhost

所有其他输入值可以保留为“未知”。

归根结底,我们只想允许具有我们知道的证书的客户端连接到代理,因此此时将client.pem上面生成的复制到代理并通过以下方式将其添加到信任库

keytool -import -alias client -keystore broker.ts -file client.pem

如果代理要允许来自任何客户端的连接,则可以跳过最后一步。

设置 ApacheMQ

默认情况下,所有通过 STOMP 的连接(实际上是所有连接)都是明文连接,为了启用 SSL 上的 STOMP 连接,请将以下内容添加<transportConnector />conf/apachemq.xml

<transportConnectors>
    <transportConnector name="stomp+ssl" uri="stomp+nio+ssl://0.0.0.0:61613?transport.enabledProtocols=TLSv1.2&amp;needClientAuth=true" />
</transportConnectors>

确保删除任何现有的纯文本连接器,例如默认的 STOMP 连接器,否则客户端将能够简单地使用这些连接器并绕过 SSL 要求。另请注意,这needClientAuth=true就是强制客户端证书验证的原因;没有这个,客户端可以在不提供可信证书的情况下进行连接。

要将 ApacheMQ 配置为使用上面定义的密钥和信任存储,请ACTIVEMQ_SSL_OPTS通过(在 Unix 上)定义环境变量

export ACTIVEMQ_SSL_OPTS = -Djavax.net.ssl.keyStore=/path/to/broker.ks -Djavax.net.ssl.trustStore=/path/to/broker.ts -Djavax.net.ssl.keyStorePassword=passwordForBrokerKs -Djavax.net.ssl.trustStorePassword=passwordForBrokerTs

或(在 Windows 上)

set ACTIVEMQ_SSL_OPTS=-Djavax.net.ssl.keyStore=C:\path\to\broker.ks -Djavax.net.ssl.trustStore=C:\path\to\broker.ts -Djavax.net.ssl.keyStorePassword=passwordForBrokerKs -Djavax.net.ssl.trustStorePassword=passwordForBrokerTs

这里,这两个密码是keytool在上一步运行后选择的。如果不需要客户端证书验证,只需省略 和 的trustStore配置trustStorePassword

有了这个,ActiveMQ 可以像往常一样通过bin/activemq start. 为确保 SSL 配置符合预期,请注意JVM args启动服务器时打印的输出部分。

测试 STOMP 客户端

正确设置代理后,我们也可以配置客户端。在这里,我们提供stomp.Connection.set_ssl了对第一步中创建的密钥和证书的引用。假设 ActiveMQ 服务器在 localhost:61613 上运行,您的测试脚本就变成了

import time
import sys
import stomp

class MyListener(stomp.ConnectionListener):
    def on_error(self, headers, message):
        print('received an error "%s"' % message)
    def on_message(self, headers, message):
        print('received a message "%s"' % message)

host = 'localhost'
port = 61613
conn = stomp.Connection([(host, port)])
conn.set_ssl(for_hosts=[(host, port)], key_file='/path/to/client.key', cert_file='/path/to/client.pem')
conn.set_listener('', MyListener())
conn.start()
conn.connect('admin', 'password', wait=True)
conn.subscribe(destination='/queue/test', id=1, ack='auto')
conn.send(body='test message', destination='/queue/test')
time.sleep(2)
conn.disconnect()

为了确保 ApacheMQ 确实在验证客户端证书,我们可以重复第 1 步并创建一个新对,client2.key例如client2.pem,然后使用它。这样做应该会导致 ApacheMQ 打印以下无意义的错误消息:

ERROR | Could not accept connection from null : {}
java.io.IOException: javax.net.ssl.SSLHandshakeException: General SSLEngine problem

验证经纪人证书

现在,细心的读者会注意到,我们实际上从未将代理证书移动到客户端,但无论如何事情似乎都可以正常工作。事实证明,默认行为stomp.py是根本不执行证书验证,从而允许(主动)攻击者 MITM 连接。

由于我们正在滚动自签名证书,因此我们需要做的就是向 Python 客户端提供实际的代理证书。在broker上,通过导出证书

keytool -exportcert -rfc -alias broker -keystore broker.ks -file broker.pem

并移至broker.pemPython 客户端。现在,在上面的测试脚本中,通过将 SSL 配置替换为

conn.set_ssl(for_hosts=[(host, port)], key_file='/path/to/client.key', cert_file='/path/to/client.pem', ca_certs='/path/to/broker.pem')

如上所述,我们可以通过重复代理证书生成过程来测试这是否确实执行了正确的验证,以创建一个broker2.pem,在测试脚本中使用它,并注意它将失败并显示一个

ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:833)
于 2018-06-09T13:28:13.333 回答
2

尝试这个。

 conn = stomp.Connection([(host, port)])
 conn.set_listener('', MyListener())

 conn.set_ssl(for_hosts=[(host, port)], ssl_version=ssl.PROTOCOL_TLS)

 conn.start()
 conn.connect(login, password, wait=True)
 conn.send(body=message, destination=queue)
 conn.disconnect()

或者

conn.set_ssl(for_hosts=[(host, port)], ssl_version=_ssl.PROTOCOL_TLS)
于 2018-12-03T12:52:12.047 回答
0

关键点是了解密钥/证书文件必须存在 - 否则即使在其存储库中的 stomp.py 测试也将无法正常工作。如果未指定(正确)文件名,TLS 将无法在 stomp.py 中工作。

请参阅此处的代码和密钥生成说明:https ://gist.github.com/borislitvak/6ccea503abf1b2f9c89e87309d6dab88

于 2020-11-10T10:59:59.133 回答