简体   繁体   English

在 Java 中选择 SSL 客户端证书

[英]Choosing SSL client certificate in Java

Our system communicates with several web services providers.我们的系统与多个网络服务提供商进行通信。 They are all invoked from a single Java client application.它们都是从单个 Java 客户端应用程序调用的。 All the web services up until now have been over SSL, but none use client certificates.到目前为止,所有 Web 服务都通过 SSL,但没有一个使用客户端证书。 Well, a new partner is changing that.好吧,一个新的合作伙伴正在改变这一点。

Making the application use a certificate for the invocation is easy;让应用程序使用证书进行调用很容易; setting javax.net.ssl.keyStore and javax.net.ssl.keyStorePassword will do it.设置javax.net.ssl.keyStorejavax.net.ssl.keyStorePassword就可以了。 However, the problem is now how to make it so that it only uses the certificate when invoking that particular web service.然而,现在的问题是如何使它只在调用该特定 Web 服务时使用证书。 I guess more generally speaking, we'd like to be able to choose the client certificate to be used, if any.我想更一般地说,我们希望能够选择要使用的客户端证书(如果有的话)。

One quick solution could be setting the system properties, invoking the methods, and then unsetting them.一种快速的解决方案是设置系统属性,调用方法,然后取消设置。 The only problem with that is that we're dealing with a multi-threaded application, so now we would need to deal with synchronization or locks or what have you.唯一的问题是我们正在处理一个多线程应用程序,所以现在我们需要处理同步或锁或你有什么。

Each service client is supposed to be completely independent from each other, and they're individually packaged in separate JARs.每个服务客户端都应该彼此完全独立,并且它们被单独打包在单独的 JAR 中。 Thus, one option that has occurred to me (although we haven't properly analyzed it) is to somehow isolate each JAR, maybe load each one under a different VM with different parameters.因此,我想到的一个选择(尽管我们还没有正确分析它)是以某种方式隔离每个 JAR,也许将每个 JAR 加载到具有不同参数的不同 VM 下。 That's merely an idea that I don't know how to implement (or if it's even possible, for that matter.)这只是一个我不知道如何实现的想法(或者如果它甚至可能,就此而言。)

This post suggests that it is possible to select an individual certificate from a key store, but how to attach it to the request seems to be a different issue altogether. 这篇文章表明可以从密钥库中选择一个单独的证书,但是如何将其附加到请求中似乎完全是一个不同的问题。

We're using Java 1.5, Axis2, and client classes generated with either wsimport or wsdl2java .我们正在使用 Java 1.5、Axis2 和使用wsimportwsdl2java生成的客户端类。

The configuration is done via an SSLContext , which is effectively a factory for the SSLSocketFactory (or SSLEngine ).配置是通过SSLContext完成的,它实际上是SSLSocketFactory (或SSLEngine )的工厂。 By default, this will be configured from the javax.net.ssl.* properties.默认情况下,这将通过javax.net.ssl.*属性进行配置。 In addition, when a server requests a certificate, it sends a TLS/SSL CertificateRequest message that contains a list of CA's distinguished names that it's willing to accept.此外,当服务器请求证书时,它会发送一条 TLS/SSL CertificateRequest消息,其中包含它愿意接受的 CA 可分辨名称列表。 Although this list is strictly speaking only indicative (ie servers could accept certs from issuers not in the list or could refuse valid certs from CAs in the list), it usually works this way.虽然这个列表严格来说只是指示性的(即服务器可以接受来自不在列表中的颁发者的证书,或者可以拒绝来自列表中的 CA 的有效证书),但它通常以这种方式工作。

By default, the certificate chooser in the X509KeyManager configured within the SSLContext (again you normally don't have to worry about it), will pick one of the certificates that has been issued by one in the list (or can be chained to an issuer there).默认情况下,在SSLContext中配置的X509KeyManager中的证书选择器(同样您通常不必担心),将选择列表中的一个已颁发的证书之一(或者可以链接到颁发者那里)。 That list is the issuers parameter in X509KeyManager.chooseClientAlias (the alias is the alias name for the cert you want to picked, as referred to within the keystore).该列表是X509KeyManager.chooseClientAlias issuersalias是您要选择的证书的别名,如密钥库中所指)。 If you have multiple candidates, you can also use the socket parameter, which will get you the peer's IP address if that helps making a choice.如果您有多个候选人,您还可以使用socket参数,如果这有助于您做出选择,它将获得对等方的 IP 地址。

If this helps, you may find using jSSLutils (and its wrapper) for the configuration of your SSLContext (these are mainly helper classes to build SSLContext s).如果这有帮助,您可能会发现使用jSSLutils(及其包装器)来配置SSLContext (这些主要是构建SSLContext的辅助类)。 (Note that this example is for choosing the server-side alias, but it can be adapted, the source code is available .) (注意这个例子是选择服务器端的别名,但可以修改, 源代码可用。)

Once you've done this, you should look for the documentation regarding the axis.socketSecureFactory system property in Axis (and SecureSocketFactory ). 完成此操作后,您应该在 Axis(和 SecureSocketFactory )中查找有关 axis.socketSecureFactory系统属性的文档。 If you look at the Axis source code, it shouldn't be too difficult to build a org.apache.axis.components.net.SunJSSESocketFactory that's initialized from the SSLContext of your choice (see this question ). 如果您查看 Axis 源代码,构建一个从您选择的 SSLContext初始化的 org.apache.axis.components.net.SunJSSESocketFactory应该不会太难(参见 这个问题)。

Just realized you were talking about Axis2, where the SecureSocketFactory seems to have disappeared.刚刚意识到您在谈论 Axis2, SecureSocketFactory似乎已经消失了。 You might be able to find a workaround using the default SSLContext , but this will affect your entire application (which isn't great).您也许可以使用默认的SSLContext找到解决方法,但这会影响您的整个应用程序(这不是很好)。 If you use a X509KeyManagerWrapper of jSSLutils, you might be able to use the default X509KeyManager and treat only certain hosts as an exception.如果您使用jSSLutils的 X509KeyManagerWrapper,您可能能够使用默认的X509KeyManager并且仅将某些主机视为例外。 (This is not an ideal situation, I'm not sure how to use a custom SSLContext / SSLSocketFactory in Axis 2.) (这不是一个理想的情况,我不确定如何在 Axis 2 中使用自定义SSLContext / SSLSocketFactory 。)

Alternatively, according to this Axis 2 document , it looks like Axis 2 uses Apache HTTP Client 3.x:或者,根据 此 Axis 2 文档,看起来 Axis 2 使用 Apache HTTP Client 3.x:

If you want to perform SSL client authentication (2-way SSL), you may use the Protocol.registerProtocol feature of HttpClient.如果你想进行 SSL 客户端认证(2-way SSL),你可以使用 HttpClient 的 Protocol.registerProtocol 特性。 You can overwrite the "https" protocol, or use a different protocol for your SSL client authentication communications if you don't want to mess with regular https.如果您不想与常规 https 混淆,您可以覆盖“https”协议,或者为您的 SSL 客户端身份验证通信使用不同的协议。 Find more information at http://jakarta.apache.org/commons/httpclient/sslguide.htmlhttp://jakarta.apache.org/commons/httpclient/sslguide.html找到更多信息

In this case, the SslContextedSecureProtocolSocketFactory should help you configure an SSLContext .在这种情况下, SslContextedSecureProtocolSocketFactory应该帮助您配置SSLContext

Java SSL clients will only send a certificate if requested by the server. Java SSL 客户端仅在服务器请求时才会发送证书。 A server can send an optional hint about what certificates it will accept;服务器可以发送关于它将接受哪些证书的可选提示; this will help a client choose a single certificate if it has multiple.如果有多个证书,这将帮助客户选择一个证书。

Normally, a new SSLContext is created with a specific client certificate, and Socket instances are created from a factory obtained from that context.通常,使用特定客户端证书创建新的SSLContext ,并从从该上下文获取的工厂创建Socket实例。 Unfortunately, Axis2 doesn't appear to support the use of an SSLContext or a custom SocketFactory .不幸的是,Axis2 似乎不支持使用SSLContext或自定义SocketFactory Its client certificate settings are global.它的客户端证书设置是全局的。

I initialized EasySSLProtocolSocketFactory and Protocol instances for different endpoints and register the protocol with unique key like this:我为不同的端点初始化了 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;
}   

When I have to invoke the web service, I do this (which basically replace "https" in the URL with https1, or https2 or something else depending on the Protocol you initialized for that particular endpoint):当我必须调用 Web 服务时,我会这样做(基本上将 URL 中的“https”替换为 https1、https2 或其他内容,具体取决于您为该特定端点初始化的协议):

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

It works like a charm!它就像一个魅力!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM