级别: 中级 Steve Russell, 软件测试、推广和高级消息传递技术人员, IBM
2009 年 9 月 15 日 随 IBM® Lotus® Expeditor 6.2 一起发布的微代理(micro broker)v3 提供了一项新的安全特性,即使用业界标准 Transport Layer Security/Secure Sockets Layer(TLS/SSL)协议对在代理中进出的网络数据进行加密的能力。本文描述使用这项新特性保护微代理通信的几种不同的方式。
在微代理中使用 TLS/SSL
本文并不是介绍 TLS/SSL 及其所有功能的教程。可用于(并且应该用于)确保安全通信、最小化数据拦截和危害的方法有很多种,要完整地描述这些方法,需要一本书的篇幅,而且一些著作中已经有这方面的描述,读者自己可以去获得必要的专门知识。
本文描述使用新的安全特性确保微代理通信安全的几种方法。在简要介绍 SSL 连接类型之后,本文提供特定的代码示例,以帮助您快速、安全地开始入手。
Transport Layer Security 与它的前身 Secure Sockets Layer 常常被统称为 SSL。这里对 SSL 这个术语的使用并不完全正确,但本文后面仍将沿用这一惯例。当您熟悉微代理安全性 API 时,就可以看到,程序员可以决定用于特定链接的实际协议。
SSL 连接类型
通常的 SSL 连接可分为以下三种类型:
- 加密的通信,没有认证
- 加密的通信,服务器认证
- 加密的通信,相互认证
第一类连接并不常用,因为认证是 SSL 的一项重要的功能,为实体证明自身与其声明的身份相符提供一种机制。第二种连接,即服务器认证,是通过由服务器向客户机提供一个声明所有者组织的身份的凭证来实现的。有效的凭证包括一个从所有者组织到凭证授权中心(CA)的信任链,凭证授权中心是一个权威的、受信任的第三方。凭证中还包括服务器的公共密钥,用于发起加密的数据流。
您可能已经在上网的过程中熟悉了凭证。当您偶然访问一个新的 Web 站点时,会看到一个窗口,询问您是否信任该站点提供的凭证。Web 浏览器通常被配置为能够识别很多知名的 CA,但是如果站点没有一个有效的、已知的凭证(凭证可能过期、来自未知的 CA、自我认证等),则向用户显示一条消息。这种情况下,要由用户查看凭证,并决定是否信任该凭证以及提供该凭证的服务器。
服务器认证是最常用的 SSL 连接。这种认证提供了一种方式,使客户机可以确保正在与正确的服务器通信,而不是与企图拦截通信的假冒服务器进行通信。在某些情况下,服务器可能还需要确信客户机与其声明的身份相符,因而需要相互认证的连接。这就是前面列出的第三种连接。选择使用哪种类型的 SSL 连接,这是实现者在系统设计时面临的问题。本文后面将描述设置服务器认证和相互认证这两种连接的一些简单方式。
服务器认证
如前所述,支持 SSL 连接的服务器必须向用户或客户机提供一个凭证。凭证中包含所有者的公共密钥,用于发起客户机与服务器之间加密的通信。建立连接后,在余下的会话中将使用新的随机密钥进行数据交换。
密钥和凭证存放在 keystore 和 truststore 文件中。服务器将它的私钥和包含公共密钥(被发送给客户机)的证书保存在 keystore 中。当客户机收到凭证时,如果客户机选择信任该凭证,那么凭证被存放在客户机的 truststore 中。
Java™ 提供 keytool 实用程序来管理密钥、凭证、keystore 和 truststore。要了解关于以下命令中使用的选项的信息,请参阅 keytool 文档。对于文件名和其他参数,必要时应该替换为您自己的值。
注意:可以将 Lotus Expeditor 配置为使用不同的 Java Virtual Machines(JVM)。标准的安装将 Lotus Expeditor 配置为使用 DesktopEE JVM。用于 DesktopEE 环境的 keystores、truststores 和 keytool 实用程序与 Java 2 Platform, Standard Edition (J2SE)环境不兼容,反之亦然。请使用与当前环境中使用的 JVM 相匹配的 keytool 实用程序来处理 keystore。因此,不能将 keystores 和 truststores 从一个环境转移到另一个环境。如果要同时支持两个环境,必须为每个环境单独创建必要的密钥和 truststore。
注意,keytool 命令应该在一行中输入。这里使用的 keytool 选项已经在 DesktopEE 和 J2SE 环境中经过测试。这里选择的选项不一定适用于所有可用的选项。
您应该仔细阅读 keytool 文档,理解所使用的选项。当然,密码和其他用户提供的数据应该由您自己选择。
创建启用 SSL 的端口
遵循以下步骤,创建使用服务器认证、具有最低 SSL 配置的安全的客户机微代理链接。
创建微代理的 keystore
如果当前没有用于您的微代理的认证,那么首先创建微代理的私钥/公共密钥对和凭证,并将它们放在一个 keystore 中。调用 keytool 命令就可以完成这项任务:
keytool -genkey -alias examplecert -keystore mbrokerdtee.jks
-keypass default -storepass default -dname "CN=Micro broker, OU=PVC,
O=IBM, C=UK" -keyalg RSA
该命令完成时,将创建 mbrokerdtee.jks 文件,其中包含代理的密钥对和一个匹配的自签名凭证。
创建一个安全的侦听器
新的默认的微代理在端口 1883 上通过一个无加密的 TCPListener 创建。可以用两种方式创建安全的侦听器:
- 将安全的 TCPListener 添加到一个正在运行的代理中。
- 可以通过默认的安全 TCPListener(和无加密的 TCPListeners)创建代理。
我们将逐一讨论这两种选项。
首先,将一个新的安全 TCPListener 添加到一个已有的微代理中,如清单 1 所示。在这个例子中,假设代码与代理在同一个 JVM 中运行,所以包含一个 LocalBroker 对象引用。在实际中,lbroker 可以是对 RemoteBroker 对象的一个引用。无论如何,在将侦听器添加到被引用的代理之前,被引用的代理必须处于运行状态。
清单 1. 将一个安全的侦听器添加到微代理中
private final String BROKER_NAME = "MBroker";
private final int SECURE_PORT = 8883;
private final String KEYSTORE_DTEE = "/mbrokerdtee.jks";
private final char[] KSPASSWD_DTEE ={'d','e','f','a','u','l','t'};
void _addSecurePort() throws Exception
{
BrokerFactory factory = BrokerFactory.INSTANCE;
LocalBroker lbroker = factory.getByName(BROKER_NAME);
Communications bc = lbroker.getCommunications();
// Specify the port number in this method call. 8883 is the IANA
// default port for secure MQTT connections.
MQTTTCPListenerDefinition mqttld =
bc.createMQTTTCPListenerDefinition(SECURE_PORT);
mqttld.setSecure(true);
ServerSSLDefinition ssld = mqttld.createSSLDefinition();
ssld.setKeyStore(KEYSTORE_DTEE);
ssld.setKeyStorePassword(KSPASSWD_DTEE);
mqttld.setSSLDefinition(ssld);
bc.addTCPListener(mqttld);
}
|
通过这个技巧可以在指定的端口上创建一个新的、加密的侦听器,并且使代理的无加密的端口(默认情况下为 1883)仍然可用,如果需要从 Internet 提供安全的访问,并且只允许从受控制的位置对无安全保障的端口进行访问,那么这种方法比较合适。如果您对这种类型的配置感兴趣,请参阅后面的小节 “将侦听器绑定到特定的 IP 地址”。
有了 BrokerFactory 对象的引用(在这个例子中为 factory)后,创建默认的安全代理不再困难。在生产代码中,可以通过 OSGi Service Registry 获得这个引用。为了更加清晰,清单 2 展示的例子获得一个直接的引用。如果不希望加密任何到微代理的连接,应该创建一个默认的安全代理。
清单 2. 创建默认的安全微代理
void createSecureBroker() throws Exception
{
BrokerFactory factory = BrokerFactory.INSTANCE;
BrokerDefinition def = factory.createBrokerDefinition(BROKER_NAME);
// setPort() is optional – 8883 is used if this method is not called
// def.setPort(Integer.parseInt(SECURE_PORT));
def.setDefaultListenerSecure(true);
ServerSSLDefinition ssld = def.createSSLDefinition();
ssld.setKeyStore(KEYSTORE_DTEE);
ssld.setKeyStorePassword(KSPASSWD_DTEE);
def.setSSLDefinition(ssld);
factory.create(def);
}
|
当代理的安全侦听器成功启动后,有两条消息被写到代理的日志中:
>Info [3810] FMBD1128 MBroker Keystore '/mbrokerdtee.jks' will be used for SSL
>Info [3810] FMBD1125 MBroker Listening on port 8883 using SSL
如果出现问题(例如不能打开 keystore),则有一个错误被写到日志中。
>Error [3810] FMBD3020 MBroker SSL-Initialization failed. Keystore file not found.
创建与客户机的安全连接
至此,我们有了一个正在运行的代理,它在端口 8883 侦听传入的 SSL 连接请求。现在,需要一个客户机在那个端口上请求一个连接。
创建与客户机的安全连接
清单 3 中的例子创建一个 MQTTv5 客户机,并使用 8883 上的安全端口将它连接到代理。通过提供一个代理 URL 前缀 ssl:// 而不是 tcp://,表明客户机应该请求一个安全的连接。(在这个例子中)URL 剩下的部分表明要连接到与客户机在同一个系统上运行的代理,并选择默认的安全端口 8883。
连接的客户端的 SSL 设置,特别是存放凭证的 truststore,由 Lotus Expeditor 提供(请参阅后面的小节 “SSL 设置”)。
将 SSL 属性传递给不同类型的微代理客户机(MQTTv5、JMS 和 MQTTv3)的方式相差不大。要了解更多信息,请参阅后面的小节 “JVM 和 MQTTv3 客户机”。
清单 3. 与安全的客户机建立连接
private MqttClient myClient;
private static final String BROKER_URL = "ssl://localhost:8883";
private static final String CLIENT_ID = "AnonUser";
public void createClient() throws Exception
{
// Define the connection options for the MQTTv5 client
// We'll connect anonymously, so we don't set username/password
MqttConnectOptions connOpts = new MqttConnectOptions();
connOpts.setPurge(true);
// Create the client object
myClient = new MqttClient(BROKER_URL, CLIENT_ID);
// We've created the client. For normal use, we'd also define and set
// a callback handler, but we're only connecting so won't bother.
myClient.connect(connOpts);
}
|
当最后一条语句执行时,客户机中显示一个窗口,询问是否信任代理的凭证。在 Microsoft® Windows® 上,这个窗口看上去如图 1 所示。
图 1. 安全警告窗口
由于凭证是自签名的(也就是说,不是由一个 CA 签名的),因此没有建立信赖。因此,首先允许用户获得凭证,由用户决定是否信任它。默认选项是仅对当前会话信任凭证。但是,如果选择 Do not trust this certificate 选项,则凭证被拒绝,SSL 连接未能建立。无论选择哪个选项,都意味着当关闭并重新启动 Lotus Expeditor 时,下一次客户机尝试连接到微代理时仍会出现这个窗口。
最后一个选项是 Trust this certificate,该选项告诉 Lotus Expeditor 您相信凭证是真的。于是,将凭证存储在客户机的 Lotus Expeditor platform truststore 中,将来客户机无需询问便可连接到微代理。
检查连接是否已建立
至此,如果一切正常,应该已经建立与客户机的连接,并将以下消息写到微代理的日志中:
>Info [3810] FMBT1741 MBroker Client 'AnonUser'
connected on port 8883 from /127.0.0.1:0
创建安全的桥连接
有些系统拓扑需要一个微代理,以便使用微代理的桥功能连接到其他微代理或消息代理。可以使用 SSL 保障与微代理或 IBM WebSphere® MQ 的桥连接的安全。清单 4 展示了用于设置 SSL 桥连接的代码。
创建桥连接
在这个例子中,远程代理是前面创建的 MBroker,它有一个默认的安全端口 8883。另一个微代理 LocalBroker 在同一个系统上运行,它侦听一个不同的端口。该代码创建从 LocalBroker 到 MBroker 的安全端口的一个安全的 MQTTv5 管道(桥连接)。可以使用相同的技巧创建 MQTTv3 和 MQJMS 桥连接。请查看清单 4 中的代码。
创建桥连接与创建客户机连接的主要区别在于,远程代理的主机名(或 IP 地址)被传递到桥连接,而不是它的 URL。这个区别意味着不能使用 ssl:// 前缀来表明需要安全连接。相反,这项任务是由 MQTTConnectionDefinition 对象上的 setSecure(true) 方法调用来完成的。
清单 4. 创建安全的桥连接
private final String BROKER_NAME = "LocalBroker";
private final int REMOTE_SECURE_PORT = 8883;
public void _createSecurePipe(CommandInterpreter ci) throws Exception
{
BrokerFactory factory = BrokerFactory.INSTANCE;
LocalBroker lbroker = factory.getByName(BROKER_NAME);
bridge = lbroker.getBridge();
PipeDefinition pipeDef = bridge.createPipeDefinition("securePipe");
MQTTConnectionDefinition secBridge =
bridge.createMQTTConnectionDefinition("secConn");
// Uncomment this method call for an MQTTv3 connection
// secBridge.setProtocolVersion(3);
secBridge.setHost("localhost");
secBridge.setPort(REMOTE_SECURE_PORT);
secBridge.setSecure(true);
pipeDef.setConnection(secBridge);
// A pipe has to have at least one flow...
FlowDefinition outflow = bridge.createFlowDefinition("secureflow");
outflow.setSources(new DestinationDefinition[]{
bridge.createTopicDefinition("SSLBridge/#")});
outflow.setQos(2);
pipeDef.addOutboundFlow(outflow);
Pipe pipe = bridge.addPipe(pipeDef);
pipe.start();
}
|
检查桥连接是否已建立
当管道启动时(清单 4 中的最后一行代码),LocalBroker 和常规的客户机一样遇到 Certificate trust 窗口。接受凭证后,LocalBroker 日志显示如清单 5 所示的消息。
清单 5. LocalBroker 日志消息
>Info [1240] FMBB2006 bridge Pipe securePipe initializing.
>Info [1240] FMBB2004 bridge Pipe securePipe starting.
>Info [1240] FMBB2000 bridge Pipe securePipe started.
>Info [1240] FMBB2628 bridge Pipe securePipe has established its outbound connection.
|
在远程代理(MBroker)上,日志显示连接已建立:
>Info [3810] FMBT1741 MBroker Client 'securePipe'
connected on port 8883 from /127.0.0.1:0
相互认证
在代理和客户机(或桥)之间创建相互认证的连接要稍微复杂一些。同样,代理将它用于验证的凭证提供给请求连接的客户机,客户机选择接受或拒绝凭证。在相互认证的连接中,客户机(或桥)还将它自己的凭证提供给代理。
这个事务的一个明显的区别在于,代理不需要面临接受或拒绝客户机凭证的选项。相反,在客户机请求建立相互认证连接之前,客户机的凭证必须是代理已知的(也就是说,存储在它的 truststore 中)。默认的 Lotus Expeditor truststore 是位于以下位置的 Java runtime cacerts keystore:
$Expeditor_INSTALL_DIR\rcp\eclipse\plugins\$runtime_jre\jre\lib\security\cacerts
接下来的例子将把客户机凭证存储在这个 keystore 中。
设置微代理以支持相互认证
要为与微代理的连接设置相互认证,需要定义安全端口,以便在建立连接之前由该端口检查有效的凭证。
为额外的端口设置相互认证
如清单 6 所示,只需一行代码(加粗表示)就可以将安全端口添加到正在运行的代理中:
清单 6. 将相互认证的安全侦听器添加到微代理中
private final String BROKER_NAME = "MBroker";
private final int SECURE_PORT = 8883;
private final String KEYSTORE_DTEE = "/mbrokerdtee.jks";
private final char[] KSPASSWD_DTEE ={'d','e','f','a','u','l','t'};
void _addSecurePort() throws Exception
{
BrokerFactory factory = BrokerFactory.INSTANCE;
LocalBroker lbroker = factory.getByName(BROKER_NAME);
Communications bc = lbroker.getCommunications();
// Specify the port number in this method call. 8883 is the IANA
// default port for secure MQTT connections.
MQTTTCPListenerDefinition mqttld =
bc.createMQTTTCPListenerDefinition(SECURE_PORT);
mqttld.setSecure(true);
ServerSSLDefinition ssld = mqttld.createSSLDefinition();
ssld.setKeyStore(KEYSTORE_DTEE);
ssld.setKeyStorePassword(KSPASSWD_DTEE);
ssld.setClientCertificateRequired(true);
mqttld.setSSLDefinition(ssld);
bc.addTCPListener(mqttld);
}
|
为默认的安全代理设置相互认证。
当创建需要相互认证的默认安全微代理时,前面的那行代码同样适用,如清单 7 所示。
清单 7. 创建相互认证的默认安全微代理
void createSecureBroker() throws Exception
{
BrokerFactory factory = BrokerFactory.INSTANCE;
BrokerDefinition def = factory.createBrokerDefinition(BROKER_NAME);
// setPort() is optional – 8883 is used if this method is not called
// def.setPort(Integer.parseInt(SECURE_PORT));
def.setDefaultListenerSecure(true);
ServerSSLDefinition ssld = def.createSSLDefinition();
ssld.setKeyStore(KEYSTORE_DTEE);
ssld.setKeyStorePassword(KSPASSWD_DTEE);
ssld.setClientCertificateRequired(true);
def.setSSLDefinition(ssld);
factory.create(def);
}
|
代理启动时的日志消息与之前使用服务器认证时看到的日志消息一样。
注意,如果尝试从一个没有凭证(或拥有的凭证不为微代理所知)的客户机连接到代理,那么客户机代码抛出一个 MQTTException,其中包含一个嵌套的 SSLHandshakeException。
当尝试建立的桥连接没有凭证时,以下消息(如清单 8 所示)被写到 LocalBroker(即尝试连接到安全端口的代理)日志中:
清单 8. 写到 LocalBroker 日志中的消息
> 2009/01/05 14:39:11.843 SEVERE FMBB2117 bridge MQTT connection for the pipe
securePipe threw an MqttException.
> 2009/01/05 14:39:11.859 WARNING FMBB2098 bridge Connector Class loading
threw the exception (0) - javax.net.ssl.SSLHandshakeException: The operation timed
out for the Pipe securePipe
|
当 LocalBroker 再次尝试建立连接时,这些消息每隔一定时间被写到日志中。
具有安全端口的微代理永远看不到连接尝试,因为它的底层 SSL 网络栈不会将传入的请求传递给代理。因此,没有消息会写到安全代理的日志中。
为客户机(桥)创建 keystore
如前所述,客户机(或桥)需要将一个凭证提供给远程微代理,才能建立连接。
创建客户机(或桥) keystore
和代理一样,可以使用 keytool 实用程序创建包含客户机凭证的 keystore:
keytool -genkey -alias clientcert -keystore clientdtee.jks -keypass default -storepass
default -dname "CN=MB Client, OU=PVC, O=IBM, C=UK" -keyalg RSA
该实用程序创建一个文件,其中包含客户机的密钥对和匹配的自签名凭证。
获得客户机凭证的副本
当客户机请求连接时,由于 Lotus Expeditor 平台没有提供一种方式让微代理接受凭证,因此在请求连接之前,需要将客户机凭证的一个副本放到 Lotus Expeditor truststore 中。首先,需要从客户机新创建的 keystroe 中获得凭证的一个副本。keytool 实用程序提供了这个功能:
keytool -export -alias clientcert -keystore clientdtee.jks -file clientdtee.cer
这项功能创建一个文件(clientdtee.cer),其中包含客户机凭证。
导入客户机(桥)凭证
如前所述,Lotus Expeditor 使用 cacerts 文件作为默认的 truststore。因此,需要将客户机或桥的凭证导入到 cacerts 中。
将客户机(桥)凭证导入 Lotus Expeditor truststore
可以再次使用 keytool 实用程序导入凭证:
keytool -import -alias clientcert -file clientdtee.cer -keystore cacerts
只有在您已经确认信任凭证的情况下,该命令才导入凭证。确认是一个预防步骤,如果您有任何理由怀疑凭证的真实性,还可以拒绝凭证。注意,Lotus Expeditor cacerts keystore 的默认密码是 changeit,如清单 9 所示。
清单 9. 确认信任凭证
Enter store password:
changeit
Owner : CN=MB Client,OU=PVC,O=IBM,C=UK
Issuer : CN=MB Client,OU=PVC,O=IBM,C=UK
Serial number : 4962101e
Valid from Mon Jan 05 13:50:22 GMT 2009 to Sun Apr 05 14:50:22 BST 2009
Certificate fingerprints :
[MD5] : a4:ab:83:af:14:2b:40:ae:a8:f4:67:92:d7:48:28:f8
[SHA-1] : a4:13:05:4a:b2:12:96:ee:35:ee:fd:a8:ef:83:dc:bc:0e:27:44:32
Do you trust this certificate? [no]:
yes
|
可以使用 keytool 实用程序的 list 选项验证凭证是否在 cacerts 中:
keytool -list -alias clientcert -keystore cacerts
如果凭证已经成功导入,可以看到清单 10 中显示的输出(包括客户机凭证的 MD5 签名):
清单 10. 客户机凭证的 MD5 签名
Enter store password:
changeit
clientcert, Mon Jan 05 15:31:51 GMT 2009,
trusted certificate entry, Certificate fingerprint:
[MD5] : a4:ab:83:af:14:2b:40:ae:a8:f4:67:92:d7:48:28:f8
|
通过对客户机 keystore 使用 keytool list 选项,可以验证微代理的 trust store 中的凭证是否与客户机(桥)的密钥相匹配:
keytool -list -keystore clientdtee.jks
如清单 11 所示,密钥的 MD5 hash 与凭证是匹配的。
清单 11. 密钥的 MD5 hash
Enter store password:
default
Keystore type: jks
Keystore provider: OTI
Keystore contains 1 entry:
clientcert, Mon Jan 05 13:50:22 GMT 2009, key entry,
Certificate fingerprint:
[MD5] : a4:ab:83:af:14:2b:40:ae:a8:f4:67:92:d7:48:28:f8
|
创建相互认证的客户机连接
现在,所有的部分都已经齐全,创建从客户机到代理的相互认证的连接就非常简单了。
创建相互认证的客户机连接
客户机需要知道在哪里找到提供给远程代理的自签名凭证。为此,客户机提供一个 Properties 对象,其中包含 keystore 的名称和密码,如清单 12 所示(加粗的部分)。
清单 12. 创建相互认证的客户机连接
private MqttClient myClient;
private static final String BROKER_URL = "ssl://localhost:8883";
private static final String CLIENT_ID = "AnonUser";
private static final String CLIENT_KS = “clientdtee.jks";
private static final String CLIENT_KSPW = “default”:
public void createClient() throws Exception
{
// Define the connection options for the MQTTv5 client
// We'll connect anonymously, so we don't set username/password
MqttConnectOptions connOpts = new MqttConnectOptions();
connOpts.setPurge(true);
// Now set the SSL properties for the client
Properties sslprops = new Properties();
sslprops.setProperty("com.ibm.ssl.keyStore", CLIENT_KS);
sslprops.setProperty("com.ibm.ssl.keyStorePassword", CLIENT_KSPW);
connOpts.setSSLProperties(sslprops);
// Finally create the client object
myClient = new MqttClient(BROKER_URL, CLIENT_ID);
// We've created the client. For normal use, we'd also define and set
// a callback handler, but we're only connecting so won't bother.
myClient.connect(connOpts);
}
|
代理的日志中写入的消息与服务器认证情况下是一样的。
创建相互认证的桥连接
创建桥连接同样也非常简单。在这个例子中,为了简单起见,用于客户机连接的 keystore 和凭证同样被用于桥。您可能需要为桥创建单独的密钥对/凭证和 keystore。
创建相互认证的桥连接
桥也需要知道在哪里找到它的自签名凭证。与客户机不同,桥是通过清单 13 中所示的方法调用获得该信息的。
清单 13. 创建相互认证的桥连接
private final String BROKER_NAME = "LocalBroker";
private final int REMOTE_SECURE_PORT = 8883;
private static final String BRIDGE_KS = “clientdtee.jks";
private static final String BRIDGE_KSPW = “default”:
public void _createSecurePipe(CommandInterpreter ci) throws Exception
{
BrokerFactory factory = BrokerFactory.INSTANCE;
LocalBroker lbroker = factory.getByName(BROKER_NAME);
bridge = lbroker.getBridge();
PipeDefinition pipeDef = bridge.createPipeDefinition("securePipe");
MQTTConnectionDefinition secBridge =
bridge.createMQTTConnectionDefinition("secConn");
// Uncomment this method call for an MQTTv3 connection
// secBridge.setProtocolVersion(3);
secBridge.setHost("localhost");
secBridge.setPort(REMOTE_SECURE_PORT);
secBridge.setSecure(true);
SSLDefinition ssld = secBridge.createSSLDefinition();
ssld.setKeyStore(BRIDGE_KS);
ssld.setKeyStorePassword(BRIDGE_KSPW.toCharArray());
secBridge.setSSLDefinition(ssld);
pipeDef.setConnection(secBridge);
// A pipe has to have at least one flow...
FlowDefinition outflow = bridge.createFlowDefinition("secureflow");
outflow.setSources(new DestinationDefinition[]{
bridge.createTopicDefinition("SSLBridge/#")});
outflow.setQos(2);
pipeDef.addOutboundFlow(outflow);
Pipe pipe = bridge.addPipe(pipeDef);
pipe.start();
}
|
本地和远程代理的日志消息与服务器认证情况下是一样的。
SSL 设置
SSL 连接可以从几个位置获得它的设置。这一点有时候会让人困惑。
前面已经看到,为了创建 SSL 连接,对客户机和桥的配置是类似的。但是一个重要的区别在于,SSL 设置是通过一个属性对象传递给客户机的,而桥则使用 setter 方法达到相同的目的。微代理侦听器 SSL 设置与桥使用一样的 setter 方法。
本文的例子使用最少的设置建立 SSL 连接。有很多的其他设置可用于更复杂的配置,但这超出了本文的讨论范围。
表 1 对这两种方法作了一对一的比较。
表 1. 比较客户机属性与桥 setter 方法
| 客户机属性 | 桥 setter 方法 | 注释 |
|---|
| com.ibm.ssl.enabledCipherSuites | setEnabledCipherSuites(String[] ciphers) | 指定代理用于连接客户机的密码 |
|---|
| com.ibm.ssl.keyStore | setKeyStore(String keyStore) | 设置 SSL/TLS keystore 的路径 |
|---|
| com.ibm.ssl.keyStorePassword | setKeyStorePassword(char[] keyStorePassword) | 设置装载 keystore 所需的密码 |
|---|
| com.ibm.ssl.keyStoreProvider | setKeyStoreProvider(String keyStoreProvider) | 设置 keystore provider |
|---|
| com.ibm.ssl.keyStoreType | setKeyStoreType(String keyStoreType) | 设置使用的 keystore 的类型 |
|---|
| com.ibm.ssl.keyManager | setKeyManager(String keyMgrAlgo) | 设置 keystore 管理器 |
|---|
| com.ibm.ssl.protocol | setProtocol(String protocol) | 设置应该使用的 SSL/TLS 协议的版本 |
|---|
| com.ibm.ssl.contextProvider | setSecurityProvider(String securityProvider) | 设置用于实现 TLS 协议的 security provider |
|---|
| com.ibm.ssl.trustManager | setTrustManager(String trustMgrAlgo) | 设置用于实例化 TrustManagerFactory 对象的算法,而不是使用平台提供的默认算法 |
|---|
| com.ibm.ssl.trustStore | setTrustStore(String trustStore) | 设置代理使用的 SSL/TLS truststore 的路径名 |
|---|
| com.ibm.ssl.trustStorePassword | setTrustStorePassword(char[] trustStorePassword) | 设置使用的 truststore 的密码
|
|---|
| com.ibm.ssl.trustStoreProvider | setTrustStoreProvider(String trustStoreProvider) | 设置 truststore provider |
|---|
| com.ibm.ssl.trustStoreType | setTrustStoreType(String trustStoreType) | 设置使用的 truststore 的类型 |
|---|
JMS 和 MQTTv3 客户机
本文的例子展示一个使用 SSL 连接的 MQTTv5 客户机。Micro broker JMS 和 MQTTv3 客户机也有自己的 SSL 属性,这些属性是通过一个 Properties 对象提供的,如表 2 所示。
表 2. 客户机和它们的 SSL 属性
| 客户机 | SSL 属性 |
|---|
| MQTTv5 | SSL Properties 对象(在示例中为 sslprops)是在作为参数传递给 client.connect() 方法的连接选项对象(在示例中为 connOpts)中设置的 |
|---|
| JMS | SSL Properties 对象是在 JmsConnectionFactory 中作为对象属性设置的。
((JmsConnectionFactory) cf).setObjectProperty(MQTTConstants.MQTT_SSL_PROPERTIES,sslClientProps); |
|---|
| MQTTv3 | SSL Properties 对象是在作为参数传递给 MqttClientFactory.createMqttClient() 方法的 MqttProperties 对象中设置的 |
|---|
更高级的配置
当为微代理配置 SSL 时,有很多可用选项。本节简要概述一些可用于满足特定需求的选项。
建立与 WebSphere MQ 的桥连接
微代理有一个专门的连接器用于创建与 WebSphere MQ 实例之间的管道。这种类型的连接的 SSL 设置与 MQTT 桥的 SSL 设置稍微不同。除了桥连接所需的标准属性外,还需要设置一些特定于 WebSphere MQ 的属性。特别是,连接定义需要用到 setQueueManager() 和 setChannel()。SSL 定义还要求设置启用的密码;该设置必须与 WebSphere MQ 实例上可用的那些设置相匹配。如清单 14 所示。
清单 14. 创建与 WebSphere MQ 的桥连接
private final String BROKER_NAME = "MBroker";
private String mqhost = "swtest.hursley.ibm.com";
private int mqport = 1414;
private String qmgr = "swtest";
public void _createSecureMqPipe() throws Exception
{
BrokerFactory factory = BrokerFactory.INSTANCE;
LocalBroker lbroker = factory.getByName(BROKER_NAME);
bridge = lbroker.getBridge();
PipeDefinition pipeDef = bridge.createPipeDefinition("secureMqPipe");
MQJMSConnectionDefinition remote =
bridge.createMQJMSConnectionDefinition("remote");
remote.setHost(mqhost);
remote.setPort(mqport);
remote.setQueueManager(qmgr);
remote.setChannel("SECURE.CHANNEL");
remote.setSecure(true);
SSLDefinition ssld = remote.createSSLDefinition();
ssld.setEnabledCipherSuites(new String[]{
"SSL_RSA_WITH_RC4_128_SHA"});
remote.setSSLDefinition(ssld);
pipeDef.setConnection(remote);
// A pipe has to have at least one flow...
FlowDefinition outflow = bridge.createFlowDefinition("secureflow");
outflow.setSources(new DestinationDefinition[]{
bridge.createTopicDefinition("SSLBridge/#")});
outflow.setQos(2);
pipeDef.addOutboundFlow(outflow);
Pipe pipe = bridge.addPipe(pipeDef);
pipe.start();
}
|
注意,WebSphere MQ 的默认设置是需要相互认证。在这个例子中,远程 WebSphere MQ 配置已经改为只需服务器(即远程 WebSphere MQ 实例)认证,所以没有指定桥(客户机)的 keystore。对于相互认证,必须向 WebSphere MQ 提供与桥的 keystore 中的密钥相匹配的有效桥(客户机)凭证。
将侦听器绑定到特定的 IP 地址
如果系统被分配了多个 IP 地址,这些 IP 地址也许在不同的网络上,那么可以指定侦听器在哪个 IP 地址上监视连接请求。其他 IP 地址上收到的请求将被忽略。可以通过在侦听器定义中添加一行代码(清单 15 中加粗的部分)实现这种绑定。
清单 15. 将一个侦听器添加到特定的网络接口
private final String BROKER_NAME = "MBroker";
private final int SECURE_PORT = 8883;
private final String KEYSTORE_DTEE = "/mbrokerdtee.jks";
private final char[] KSPASSWD_DTEE ={'d','e','f','a','u','l','t'};
void _addSecurePort() throws Exception
{
BrokerFactory factory = BrokerFactory.INSTANCE;
LocalBroker lbroker = factory.getByName(BROKER_NAME);
Communications bc = lbroker.getCommunications();
// Specify the port number in this method call. 8883 is the IANA
// default port for secure MQTT connections.
MQTTTCPListenerDefinition mqttld =
bc.createMQTTTCPListenerDefinition(SECURE_PORT);
mqttld.setSecure(true);
ServerSSLDefinition ssld = mqttld.createSSLDefinition();
ssld.setKeyStore(KEYSTORE_DTEE);
ssld.setKeyStorePassword(KSPASSWD_DTEE);
mqttld.setNetworkInterface("127.0.0.1");
mqttld.setSSLDefinition(ssld);
bc.addTCPListener(mqttld);
}
|
在这个例子中,端口 8883 上新的安全侦听器只对来自与微代理在同一个系统上的客户机的请求作出响应。更实际的配置应该在一个通向内部网络的端口上设置一个无安全保障的侦听器,同时在部门外或公司防火墙之外的网络上有一个安全侦听器,为来自外部组织的连接提供安全的通信。
微代理 SSL 故障排除
本节提供一些建议,帮助您解决在配置用于微代理的 SSL 连接时可能遇到的问题。这里介绍得不是很全面,但是涉及了您可能遇到的一些典型的错误。
Keytool 问题
keytool 实用程序打不开 keystore – 我收到消息 “keytool error (likely untranslated): java.io.IOException: Invalid keystore format”
出现这个问题的原因可能是您试图使用 J2SE keytool 实用程序打开一个 DesktopEE keystore。
keytool 实用程序打不开 keystore – 输入 keystore 密码后,收到消息 “keytool error: Unable to open keystore. Check the file path and password”
出现这个问题可能是因为您试图使用 DesktopEE keytool 实用程序打开一个 J2SE keystore。
SSL 连接失败问题
SSL 连接失败有很多种原因。在某些情况下,异常消息比较简单,直指问题所在,例如 “Incorrect password”。
但是有些情况下,消息不是很具体。下面有一些例子。
我收到异常消息 “SSL Handshake failure”
出现这条消息可能有很多原因。这时可以检查微代理和客户机(或桥)是否能访问它们各自的 keystores 和 truststores。
我收到异常消息 “SSL-Initialization failed. I/O problem while loading the keystore”
出现这条消息可能有很多原因。它是由底层的 SSL 代码生成的,所以,微代理不能提供任何附加的信息。一个常见的起因是代理在一个 JVM 环境中运行,但是试图使用用 keytool 创建的用于另一个 JVM 环境的 keystore。
我收到异常消息 “java.io.IOException: Invalid keystore format”
这个异常可能表明 keystore 被毁坏。如果试图在 J2SE 环境中使用 DesktopEE keystore,可能出现该异常。
微代理试图访问与您所定义的 trust 或 keystore 不同的 trust 或 keystore。
微代理可以从很多不同的位置获得 SSL 设置:
- 用于侦听器或客户机的特定的方法调用
- 代理范围的设置
- Lotus Expeditor 平台设置
- Java Virtual Machine 设置
如果在 Java 命令行设置一个系统 SSL 属性(javax.net.ssl),则会覆盖 Lotus Expeditor 平台设置。
您试图将客户机(或桥)连接到代理上的一个安全端口,但是遭到失败,并且没有看到询问是否接受代理凭证的窗口。
图 1 显示了 Security Alert 窗口的一个例子,并且提供了对不同的可用选项的描述。
如果之前曾经连接到代理,并且选择了信任代理的凭证,那么客户机将直接连接上,而不会显示图 1 所示的窗口。如果不是这样,那么您可能正在使用不是随产品一起发布的 JVM 运行 Lotus Expeditor。
信任管理器控制如何处理传入的未知凭证。Lotus Expeditor JVM 信任管理器会显示 Security Alert 窗口,而其他 JVM 可能不显示该窗口。
您用 DesktopEE keytool 实用程序创建了 keystore,并且检查过代理是在一个 DesktopEE JVM 中运行,但是仍然存在问题
当第一次用 keytool 实用程序创建密钥对时,您是否使用了 –keyalg RSA 参数?keytool 使用的默认密钥算法是 DSA,而 DesktopEE JVM 不支持该算法。
结束语
本文对如何实现通往或来自微代理的安全、支持 SSL 的连接作了简单的介绍。即使用户对于 SSL 的详细操作知之甚少,本文提供的信息仍然足以让用户能够保护网络上传输的数据。对于那些有需要并且/或者感兴趣的读者,还有很多关于 SSL 的信息需要去了解。
参考资料 学习
获得产品和技术
讨论
关于作者  | |  | Steve Russell 在 Hursley Park 的 IBM 英国实验室工作。他的专长是普及软件和高级消息传递技术领域的软件测试。 |
对本文的评价
|