在 Java 中选择 SSL 客户端证书

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3712366/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 03:49:09  来源:igfitidea点击:

Choosing SSL client certificate in Java

javaweb-servicesssl

提问by Carlos

Our system communicates with several web services providers. They are all invoked from a single Java client application. All the web services up until now have been over SSL, but none use client certificates. Well, a new partner is changing that.

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

Making the application use a certificate for the invocation is easy; setting javax.net.ssl.keyStoreand javax.net.ssl.keyStorePasswordwill do it. However, the problem is now how to make it so that it only uses the certificate when invoking that particular web service. I guess more generally speaking, we'd like to be able to choose the client certificate to be used, if any.

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

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. 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. That's merely an idea that I don't know how to implement (or if it's even possible, for that matter.)

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

This postsuggests 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 wsimportor wsdl2java.

我们正在使用 Java 1.5、Axis2 和使用wsimport或生成的客户端类wsdl2java

采纳答案by erickson

Java SSL clients will only send a certificate if requested by the server. 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.

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

Normally, a new SSLContextis created with a specific client certificate, and Socketinstances are created from a factory obtained from that context. Unfortunately, Axis2 doesn't appear to support the use of an SSLContextor a custom SocketFactory. Its client certificate settings are global.

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

回答by Bruno

The configuration is done via an SSLContext, which is effectively a factory for the SSLSocketFactory(or SSLEngine). By default, this will be configured from the javax.net.ssl.*properties. In addition, when a server requests a certificate, it sends a TLS/SSL CertificateRequestmessage that contains a list of CA's distinguished names that it's willing to accept. Although this list is strictly speaking only indicative (i.e. 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.

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

By default, the certificate chooser in the X509KeyManagerconfigured 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). That list is the issuersparameter in X509KeyManager.chooseClientAlias(the aliasis the alias name for the cert you want to picked, as referred to within the keystore). If you have multiple candidates, you can also use the socketparameter, which will get you the peer's IP address if that helps making a choice.

默认情况下,X509KeyManager配置中的证书选择器SSLContext(同样您通常不必担心),将选择已由列表中的一个颁发的证书之一(或可以链接到那里的颁发者) . 该列表是issuers参数X509KeyManager.chooseClientAlias(这alias是您要选择的证书的别名,在密钥库中引用)。如果您有多个候选人,您也可以使用该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 SSLContexts). (Note that this example is for choosing the server-side alias, but it can be adapted, the source code is available.)

如果这有帮助,您可能会发现使用jSSLutils(及其包装器)来配置您的SSLContext(这些主要是构建SSLContexts 的帮助类)。(注意这个例子是为选择服务器端别名,但可以改编,源代码可用。)

Once you've done this, you should look for the documentation regarding the axis.socketSecureFactorysystem property in Axis (and SecureSocketFactory). If you look at the Axis source code, it shouldn't be too difficult to build a org.apache.axis.components.net.SunJSSESocketFactorythat's initialized from the SSLContextof your choice (see this question).

完成此操作后,您应该查找有关axis.socketSecureFactoryAxis(和SecureSocketFactory)中系统属性的文档。如果您查看 Axis 源代码,构建org.apache.axis.components.net.SunJSSESocketFactorySSLContext您选择的初始化的 Axis 源代码应该不会太困难(请参阅此问题)。

Just realized you were talking about Axis2, where the SecureSocketFactoryseems to have disappeared. You might be able to find a workaround using the default SSLContext, but this will affect your entire application (which isn't great). If you use a X509KeyManagerWrapperof jSSLutils, you might be able to use the default X509KeyManagerand treat only certain hosts as an exception. (This is not an ideal situation, I'm not sure how to use a custom SSLContext/SSLSocketFactoryin Axis 2.)

刚刚意识到您在谈论 Axis2,而 Axis2SecureSocketFactory似乎已经消失了。您或许可以使用 default 找到一种解决方法SSLContext,但这会影响您的整个应用程序(这不是很好)。如果您使用jSSLutils的 X509KeyManagerWrapper,您可能能够使用默认值X509KeyManager并仅将某些主机视为例外。(这不是理想的情况,我不确定如何在 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. 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. Find more information at http://jakarta.apache.org/commons/httpclient/sslguide.html

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

In this case, the SslContextedSecureProtocolSocketFactoryshould help you configure an SSLContext.

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

回答by helios

I initialized EasySSLProtocolSocketFactoryand 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!

它就像一个魅力!