【问题标题】:Choosing SSL client certificate in Java在 Java 中选择 SSL 客户端证书
【发布时间】:2010-09-14 19:52:44
【问题描述】:

我们的系统与多个网络服务提供商进行通信。它们都是从单个 Java 客户端应用程序调用的。到目前为止,所有 Web 服务都通过 SSL,但没有一个使用客户端证书。嗯,一个新的合作伙伴正在改变这一点。

让应用程序使用证书进行调用很容易;设置javax.net.ssl.keyStorejavax.net.ssl.keyStorePassword 就可以了。但是,现在的问题是如何制作它,以便它仅在调用该特定 Web 服务时使用证书。我想更一般地说,我们希望能够选择要使用的客户端证书(如果有的话)。

一种快速的解决方案是设置系统属性,调用方法,然后取消设置。唯一的问题是我们正在处理一个多线程应用程序,所以现在我们需要处理同步或锁或你有什么。

每个服务客户端都应该彼此完全独立,并且它们被单独打包在单独的 JAR 中。因此,我想到的一个选择(尽管我们还没有正确分析它)是以某种方式隔离每个 JAR,也许将每个 JAR 加载到具有不同参数的不同 VM 下。这只是一个我不知道如何实现的想法(或者甚至可能,就此而言。)

This post 建议可以从密钥库中选择一个单独的证书,但如何将其附加到请求中似乎完全是一个不同的问题。

我们正在使用 Java 1.5、Axis2 和使用wsimportwsdl2java 生成的客户端类。

【问题讨论】:

    标签: java web-services ssl


    【解决方案1】:

    配置是通过SSLContext 完成的,它实际上是SSLSocketFactory(或SSLEngine)的工厂。默认情况下,这将通过javax.net.ssl.* 属性进行配置。此外,当服务器请求证书时,它会发送一条 TLS/SSL CertificateRequest 消息,其中包含它愿意接受的 CA 可分辨名称列表。虽然这个列表严格来说只是指示性的(即服务器可以接受来自不在列表中的颁发者的证书,或者可以拒绝来自列表中 CA 的有效证书),但它通常以这种方式工作。

    默认情况下,SSLContext 中配置的X509KeyManager 中的证书选择器(同样您通常不必担心),将选择列表中的一个已颁发的证书之一(或者可以链接到那里的发行人)。 该列表是X509KeyManager.chooseClientAlias 中的issuers 参数(alias 是您要选择的证书的别名,在密钥库中引用)。如果您有多个候选人,您还可以使用socket 参数,如果这有助于您做出选择,它将获得对等方的 IP 地址。

    如果这有帮助,您可能会发现使用jSSLutils (and its wrapper) 来配置您的SSLContext(这些主要是构建SSLContexts 的帮助类)。 (请注意,此示例用于选择服务器端别名,但可以修改,source code is available。)

    完成此操作后,您应该查找有关 Axis(和 SecureSocketFactory)中 axis.socketSecureFactorysystem 属性的文档。如果您查看 Axis 源代码,构建一个从您选择的 SSLContext 初始化的 org.apache.axis.components.net.SunJSSESocketFactory 应该不会太难(参见 this question)。

    刚刚意识到您在谈论 Axis2,SecureSocketFactory 似乎已经消失了。您也许可以使用默认的SSLContext 找到解决方法,但这会影响您的整个应用程序(这不是很好)。如果您使用 jSSLutils 的 X509KeyManagerWrapper,您可能能够使用默认的 X509KeyManager 并仅将某些主机视为例外。 (这不是一个理想的情况,我不确定如何在 Axis 2 中使用自定义 SSLContext/SSLSocketFactory。)

    或者,根据this Axis 2 document,看起来 Axis 2 使用 Apache HTTP Client 3.x:

    如果你想执行 SSL 客户端 身份验证(2 路 SSL),您可以 使用 Protocol.registerProtocol HttpClient 的特性。你可以 覆盖“https”协议,或使用 SSL 的不同协议 客户端身份验证通信 如果您不想与常规混淆 HTTPS。在以下位置查找更多信息 http://jakarta.apache.org/commons/httpclient/sslguide.html

    在这种情况下,SslContextedSecureProtocolSocketFactory 应该可以帮助您配置 SSLContext

    【讨论】:

    • 嗯...刚刚意识到您使用的是 Java 1.5,所以无论如何都没有SSLContext.setDefault(...)。为什么不使用 Java 6?据我所知,Java 5 已不再受支持(拥有各种安全补丁并无害处)。
    • (通过 Apache HTTP Client 3.x 设置 SSLContext 应该适用于 Java 1.5。)
    • @Carlos,没问题。通过 Apache HTTP 客户端的 Protocol.registerProtocol 更改 SSLContext 是否适用于 Axis 2(只是好奇)?
    • 其实不需要改动,但是有那些通用的cmets和input就好了。
    【解决方案2】:

    Java SSL 客户端只会在服务器请求时发送证书。服务器可以发送一个关于它将接受什么证书的可选提示;如果有多个证书,这将帮助客户选择一个证书。

    通常,使用特定客户端证书创建新的SSLContext,并从从该上下文获取的工厂创建Socket 实例。不幸的是,Axis2 似乎不支持使用SSLContext 或自定义SocketFactory。它的客户端证书设置是全局的。

    【讨论】:

    • 不幸的是,我们无法控制服务器。我也希望这些提示有用,但测试表明至少在一台服务器上它们不是。 WRT Axis2,这也是我的经验。如果您碰巧知道另一个允许这种行为的 Java 工具,我将非常感谢您的分享;我们很乐意探索它。
    • @Carlos - 也许我不清楚。您不需要对服务器进行任何控制;如果您的合作伙伴现在需要客户端证书,他们将配置他们的服务器来请求它。您的客户端将不会将该证书发送到任何其他服务器,因为它们(可能)没有请求它。唯一的潜在问题是,如果其他服务开始需要客户端证书,但不接受相同的 CA。
    • 知道了,谢谢。不过,我想另一个问题仍然存在。如果明天我们添加另一个也需要客户端证书的 Web 服务,有没有办法选择密钥存储中的哪个证书提供给每个服务?
    • 看起来Axis 2下面使用的是Apache HTTP Client 3.x,所以应该可以这样配置SSLContext(虽然我没试过)。
    【解决方案3】:

    我为不同的端点初始化了EasySSLProtocolSocketFactory 和协议实例,并使用这样的唯一键注册协议:

    /**
     * This method does the following:
     * 1. Creates a new and unique protocol for each SSL URL that is secured by client certificate
     * 2. Bind keyStore related information to this protocol
     * 3. Registers it with HTTP Protocol object 
     * 4. Stores the local reference for this custom protocol for use during furture collect calls
     * 
     *  @throws Exception
     */
    public void registerProtocolCertificate() throws Exception {
        EasySSLProtocolSocketFactory easySSLPSFactory = new EasySSLProtocolSocketFactory();
        easySSLPSFactory.setKeyMaterial(createKeyMaterial());
        myProtocolPrefix = (HTTPS_PROTOCOL + uniqueCounter.incrementAndGet());
        Protocol httpsProtocol = new Protocol(myProtocolPrefix,(ProtocolSocketFactory) easySSLPSFactory, port);
        Protocol.registerProtocol(myProtocolPrefix, httpsProtocol);
        log.trace("Protocol [ "+myProtocolPrefix+" ] registered for the first time");
    }
    
    /**
     * Load keystore for CLIENT-CERT protected endpoints
     */
    private KeyMaterial createKeyMaterial() throws GeneralSecurityException, Exception  {
        KeyMaterial km = null;
        char[] password = keyStorePassphrase.toCharArray();
        File f = new File(keyStoreLocation);
        if (f.exists()) {
            try {
                km = new KeyMaterial(keyStoreLocation, password);
                log.trace("Keystore location is: " + keyStoreLocation + "");
            } catch (GeneralSecurityException gse) {
                if (logErrors){
                    log.error("Exception occured while loading keystore from the following location: "+keyStoreLocation, gse);
                    throw gse;
                }
            }
        } else {
            log.error("Unable to load Keystore from the following location: " + keyStoreLocation );
            throw new CollectorInitException("Unable to load Keystore from the following location: " + keyStoreLocation);
        }
        return km;
    }   
    

    当我必须调用 Web 服务时,我会这样做(基本上将 URL 中的“https”替换为 https1、https2 或其他内容,具体取决于您为该特定端点初始化的协议):

    httpClient.getHostConfiguration().setHost(host, port,Protocol.getProtocol(myProtocolPrefix));
    initializeHttpMethod(this.url.toString().replace(HTTPS_PROTOCOL, myProtocolPrefix));
    

    它就像一个魅力!

    【讨论】:

    • 你能指出进口吗? EasySSLProtocolSocketFactory?关键材料?具有依赖关系的完整示例将非常好。谢谢。
    • 如果您能详细说明您的代码,那就太好了。
    • 看看 github 上的这个文件来回答你的大部分问题:github.com/nickman/helios/blob/gh-pages/helios-collectors/…
    猜你喜欢
    • 2012-02-15
    • 2015-08-07
    • 1970-01-01
    • 2013-08-04
    • 2010-10-16
    • 2017-04-28
    • 1970-01-01
    • 2010-10-26
    • 1970-01-01
    相关资源
    最近更新 更多