在Java中选择SSL客户端证书

时间:2010-09-14 19:52:45

标签: java web-services ssl

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

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

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

每个服务客户端应该完全相互独立,并且它们分别打包在单独的JAR中。因此,我发现的一个选项(虽然我们没有对其进行适当的分析)是以某种方式隔离每个JAR,可能在具有不同参数的不同VM下加载每个JAR。这仅仅是一个我不知道如何实施的想法(或者如果可能的话,就此而言。)

This post表示可以从密钥库中选择单个证书,但如何将其附加到请求似乎完全是另一个问题。

我们使用的是使用wsimportwsdl2java生成的Java 1.5,Axis2和客户端类。

3 个答案:

答案 0 :(得分:14)

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

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

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

完成此操作后,您应该在Axis(和SecureSocketFactory)中查找有关org.apache.axis.components.net.SunJSSESocketFactory系统属性的文档。如果您查看Axis源代码,那么构建从您选择的SSLContext初始化的SecureSocketFactory应该不会太困难(请参阅this question)。 < / p>

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

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

  

如果要执行SSL客户端   验证(双向SSL),你可以   使用Protocol.registerProtocol   HttpClient的功能。您可以   覆盖“https”协议,或使用   SSL的不同协议   客户端认证通信   如果你不想乱用常规   HTTPS。有关详细信息,请访问   http://jakarta.apache.org/commons/httpclient/sslguide.html

在这种情况下,SslContextedSecureProtocolSocketFactory应该可以帮助您配置{{1}}。

答案 1 :(得分:8)

Java SSL客户端仅在服务器请求时才发送证书。服务器可以发送关于它将接受哪些证书的可选提示;这将有助于客户选择单个证书(如果有多个证书)。

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

答案 2 :(得分:0)

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

/**
 * 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替换https1中的“https”,或https2或其他东西,具体取决于您为该特定端点初始化的协议):

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

它就像一个魅力!