我们将研究的第一个客户机应用程序根本不能做什么。但是,在后面的示例中我们会扩展它来阐述更高级的功能。设置 SimpleSSLClient 的目的是为了方便地添加子类。打算覆盖下面四个方法:
还记得我们是如何运行 SimpleSSLClient 的吗?命令如下:
命令简直太长了!幸运的是,该示例及接下来的示例将为您演示如何设置一个带有到 KeyStore 和 TrustStore 的硬编码路径的SSLSocketFactory
?。除了减少上述命令的长度之外,您将学习的技术将允许您设置多个?SSLSocketFactory
?对象,每个对象都带有不同的 KeyStore 和 TrustStore 设置。如果没有这种配置,JVM 中的每个安全连接必须使用相同的 KeyStore 和 TrustStore。尽管对于较小的应用程序而言这是可以接受的,但是较大的应用程序可能需要连接到多个代表许多不同用户的对等方。
对于第一个示例,我们将使用示例应用程序 CustomKeyStoreClient(可在本文的源代码中找到)来动态定义一个 KeyStore。在研究源代码之前,让我们看看正在使用的 CustomKeyStoreClient。对于这个练习,我们将指定 TrustStore 而不是 KeyStore。在 CustomKeyStoreClient 命令行上输入下列参数,我们将看到出现的结果:
假定客户机连接良好,服务器将报告说提供的证书是有效的。连接成功,因为?CustomKeyStoreClient.java
?已经硬编码了 KeyStore 的名称(clientKeys
?)和密码(?password
?)。如果您为客户机 KeyStore 选择了另外的文件名或密码,那么可以使用新的命令行选项?-ks
?和?-kspass
来指定它们。
研究一下?CustomKeystoreClient.java
?的源代码,?getSSLSocketFactory
?做的第一件事是调用助手方法?getKeyManagers()
?。稍后我们将考虑这是如何工作的;目前只是注明它返回?KeyManager
?对象数组,已经利用必需的 KeyStore 文件和密码对其进行了设置。
获得?KeyManager
?数组之后,?getSSLSocketFactory
?执行一些对所有 JSSE 定制通常都很重要的设置工作。为了构造?SSLSocketFactory
?,应用程序获取一个?SSLContext
?实例,对其进行初始化,然后使用?SSLContext
?生成一个?SSLSocketFactory
?。
当得到?SSLContext
?时,我们指定?"SSL"
?的协议;我们也可以在这放入特定的 SSL(或 TLS)协议版本,并且强制通信在特定的级别发生。通过指定?"SSL"
?,我们允许 JSSE 缺省至它能支持的最高级别。
SSLContext.init
?的第一个参数是要使用的?KeyManager
?数组。第二个参数(这里保留为 null)类似于?TrustManager
?对象数组,稍后我们将使用它们。通过让第二个参数为 null,我们告诉 JSSE 使用缺省的 TrustStore,它从?javax.net.ssl.trustStore
?和javax.net.ssl.trustStorePassword
?系统属性挑选设置。第三个参数允许我们覆盖 JSSE 的随机数生成器(RNG)。RNG 是 SSL 的一个敏感领域,误用该参数会致使连接变得不安全。我们让该参数为 null,这样允许 JSSE 使用缺省的 ― 并且安全的!―?SecureRandom
?对象。
接下来,我们将研究?getKeyManagers
?如何装入和初始化?KeyManagers
?数组。先从清单 5 中的代码开始,然后我们将讨论正在发生什么。
// Next,set up the KeyStore to use. We need to load the file into
// a KeyStore instance.
FileInputStream fis=new FileInputStream(keyStore);
KeyStore ks=KeyStore.getInstance("jks");
ks.load(fis,keyStorePassword.toCharArray());
fis.close();
// Now we initialize the TrustManagerFactory with this KeyStore
kmFact.init(ks,keyStorePassword.toCharArray());
// And now get the TrustManagers
KeyManager[] kms=kmFact.getKeyManagers();
return kms;
}
首要工作是获取?KeyManagerFactory
?,但是要这样做,我们需要知道将使用哪种算法。幸运的是,JSSE 使缺省的?KeyManagerFactory
?算法可用。可以使用?ssl.KeyManagerFactory.algorithm
?安全性属性配置缺省算法。
接下来,?getKeyManagers()
?方法装入 KeyStore 文件。这其中包括从文件建立一个?InputStream
?、获取一个?KeyStore
?实例,以及从InputStream
?装入?KeyStore
?。除了?InputStream
?,?KeyStore
?需要知道流的格式(我们使用缺省的?"jks"
?)和存储密码。存储密码必须作为字符数组提供。
要说明的一个可能很有用的窍门是,?KeyStore.load
?会获取任何?InputStream
?。您的应用程序可以从任何地方构建这些流;除了文件,您可以通过网络、从移动设备获取流,或者甚至直接生成流。
装入?KeyStore
?之后,我们使用它来初始化以前创建的?KeyManagerFactory
?。我们需要再次指定一个密码,这次是单独的证书密码。通常,对于 JSSE 而言,KeyStore 中的每个证书都需要具备与 KeyStore 本身相同的密码。自己构造?KeyManagerFactory
?可以克服这个限制。
KeyManagerFactory
?初始化之后,它通常使用?getKeyManagers()
?方法获取相应的KeyManager
?对象的数组。
对于 CustomKeyStoreClient 而言,我们已经研究了如何从任意的位置(本文使用本地文件系统)装入 KeyStore,以及如何让证书和 KeyStore 本身使用不同的密码。稍后我们将研究如何允许 KeyStore 中的每个证书拥有不同的密码。尽管在本示例中我们着重于客户机端,但是,我们可以在服务器端使用相同的技术来构建适当的?SSLServerSocketFactory
?对象。
覆盖 JSSE 的缺省 TrustStore 非常类似于我们刚才用 KeyStore 所做的工作,这并不令人惊奇。我们已经设置了 CustomTrustStoreClient(可在本文的源代码中找到)来使用硬编码的 KeyStore 和硬编码的 TrustStore。开始运行它所需做的全部工作就是输入命令:?java CustomTrustStoreClient
?。
CustomTrustStoreClient 希望 KeyStore 将是一个名为?clientKeys
?并且密码为?password
?的文件。希望 TrustStore 将是一个名为?clientTrust
,密码为?password
?的文件。就象使用 CustomKeyStoreClient 一样,可以使用?-ks
?、?-kspass
?、?-ts
?和?-tspass
?参数覆盖这些缺省值。
getSSLSocketFactory()
?在许多方面与 CustomKeyStoreClient 中相同方法是一样的。我们甚至从 CustomKeyStoreClient 调用getKeyManagers()
?方法来获取与前面示例中相同的定制的?KeyManager
?对象数组。但是这时?getSSLSocketFactory
?还必须获取一个定制的TrustManager
?对象数组。在清单 6 中,我们可以看到?getSSLSocketFactory
?如何使用助手方法?getTrustManagers()
?获取定制的TrustManager
?对象:
// Call getKeyManagers (from CustomKeyStoreClient) to get suitable
// key managers
KeyManager[] kms=getKeyManagers();
// Next construct and initialize a SSLContext with the KeyStore and
// the TrustStore. We use the default SecureRandom.
SSLContext context=SSLContext.getInstance("SSL");
context.init(kms,tms,and pass it to SimpleSSLClient.
SSLSocketFactory ssf=context.getSocketFactory();
return ssf;
}
这时,当初始化上下文时,我们覆盖了 KeyStore 和 TrustStore。但是,我们仍然让 JSSE 通过传递?null
?作为第三个参数来使用它缺省的SecureRandom
?。
getTrustManagers
?也非常类似于 CustomKeyStoreClient 的等价物同样不足为奇,如清单 7 所示:
// Next,set up the TrustStore to use. We need to load the file into
// a KeyStore instance.
FileInputStream fis=new FileInputStream(trustStore);
KeyStore ks=KeyStore.getInstance("jks");
ks.load(fis,trustStorePassword.toCharArray());
fis.close();
// Now we initialize the TrustManagerFactory with this KeyStore
tmFact.init(ks);
// And now get the TrustManagers
TrustManager[] tms=tmFact.getTrustManagers();
return tms;
}
就象以前一样,?getTrustManagers()
?方法首先根据缺省算法实例化一个?TrustManagerFactory
?。然后将 TrustStore 文件装入?KeyStore
?对象 ― 是的,命名不大恰当 ― 并且初始化?TrustManagerFactory
?。
跟 KeyStore 等价物不同,请注意,当初始化?TrustManagerFactory
?时,无需提供密码。不象私钥,可信的证书无需利用单独的密码进行保护。
到目前为止,我们已经研究了如何动态地覆盖 KeyStore 和 TrustStore。到这两个示例都完成时,您应该非常清楚如何设置KeyManagerFactory
?和?TrustManagerFactory
?,并使用这些来“播种”一个?SSLContext
?。最后的示例有点烦琐:我们将构建自己的KeyManager
?实现。
当运行客户机应用程序的以前版本时,您是否注意到了服务器显示的是哪个证书 DN?我们故意设置客户机 KeyStore 以获得两个可接受的证书,一个用于 Alice,另一个用于 Bob。在这个案例中,JSSE 将选择任何一个它认为合适的证书。在我的安装中,似乎始终选取 Bob 的证书,但是您的 JSSE 的行为可能有所不同。
我们的示例应用程序 ― SelectAliasClient 允许您选择提供哪个证书。因为我们在 Keystore 中按照别名?alice
?或?bob
?命名了每个证书,所以要选择 Alice 的证书可输入命令:?java SelectAliasClient -alias alice
?。
当客户机连接并且 SSL 握手完成时,服务器将用如下所示进行响应:
(或者创建 Alice 的证书时所选的任何值)。类似地,如果选择 Bob 的证书,请输入:?java SelectAliasClient -alias bob
?,服务器将报告下述信息:
为了强制选择一个特殊的别名,我们将编写一个?X509KeyManager
?实现,?KeyManager
?通常由 JSSE 使用来进行 SSL 通信。我们的实现将包含一个真正的?X509KeyManager
?,并且简单地通过它传递大多数的调用。它拦截的唯一方法是?chooseClientAlias()
?;我们的实现检查以便了解所需的别名有效还是无效,如果有效,则返回它。
在 SSL 握手阶段,?X509KeyManager
?接口使用许多方法来检索密钥,然后使用它来标识对等方。在?部分可以找到所有方法的参考。下列方法对于本练习很重要:
getClientAliases()
?和?getServerAliases()
?分别为使用?SSLSocket
?和?SSLServerSocket
?提供了有效的别名数组。
-
chooseClientAlias()
?和?chooseServerAlias()
?返回单个有效的别名。
-
getCertificateChain()
?和?getPrivateKey()
?每个都把别名作为参数,并返回有关已标识证书的信息。
控制流的工作如下所示:
- JSSE 调用?
chooseClientAlias
?以发现要使用的别名。
-
chooseClientAlias
?在真实的?X509KeyManager
?上调用?getClientAliases
?来发现一个有效的别名列表,以便于它能检查所需的别名是否有效。
- JSSE 通过指定正确的别名调用?
X509KeyManager
?的?getCertificateChain
?和?getPrivateKey
?,X509KeyManager 让调用可以访问被包装的 KeyManager。
KeyManager?AliasForcingKeyManager()
?的?chooseClientAlias()
?方法实际上需要多次调用?getClientAliases()
?,一次对应一个 JSSE 安装支持的密钥类型,如清单 8 所示:
AliasForcingKeyManager
?需要?X509KeyManager
?的其它五种方法的实现;这些只是调用它们在?baseKM
?上的对应部分。
目前,它仍然使用?AliasForcingKeyManager
?,而不是通常的?KeyManager
?。这发生在?getSSLSocketFactory
?中,它首先从其它示例中调用getKeyManagers
?和?getTrustManagers
?,但是接着将每个从?getKeyManagers
?返回的?KeyManager
?封装进一个?AliasForcingKeyManager
?实例,如清单 9 所示:
可以使用本文探讨的技术覆盖?KeyManager
?的任何方面。类似地,可以使用它们代替TrustManager
?,更改 JSSE 的机制以决定是否信任从远程对等方流出的证书。
本文已经讨论了相当多的技巧和技术,因此让我们以快速回顾来结束本文。现在您应当基本了解如何:
使用?HandshakeCompletedListener
?收集有关连接的信息
- 从?
SSLContext
?获取?SSLSocketFactory
- 使用定制、动态的 TrustStore 或 KeyStore
- 放宽 KeyStore 密码与单个证书密码必须匹配的 JSSE 限制
- 使用您自己的 KeyManager 强制选择标识证书
在适当的地方,我还建议扩展这些技术以用于各种应用程序案例。在您自己的实现中封装?X509KeyManager
?的技巧可用于 JSSE 中的许多其它类,当然,利用?TrustStore
?和?KeyStore
?可以做更有趣的事情,而不只是装入硬编码的文件。
不管您如何选择实现本文演示的高级 JSSE 定制,任何一个都不是随便就可以实现的。在调整 SSL 内部机理的时候,请牢记:一个错误就会致使您连接变得不安全,这很重要。
转自:http://www.ibm.com/developerworks/cn/java/j-customssl/
http://lyb520320.iteye.com/blog/720478
(编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!