SOAP 客户端无法使用相互 tls 进行身份验证

SOAP client is not able to authenticate with mutual tls

我有一个 Apache CXF 客户端正在连接 SOAP 服务,并使用双向 TLS 进行身份验证。客户端在 TLS 握手期间失败,因为该服务向服务器发送了一个空的客户端证书列表。我正在使用自签名证书对此进行测试,并且我可以证明我的服务器可以使用 curl 请求和邮递员。我非常确定证书设置正确,而且我确定我在 CXF 客户端中缺少配置步骤。

这是我的客户端设置方式

// setting up certs & keystores
String keystore = "client-keystore.jks";
String keystorePassword = "changeit"; // local self-signed certs

String trustStore = "truststore.jks";
String trustStorePassword = "changeit"; // local self-signed certs

// client keystore
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(keystore), keystorePassword.toCharArray());

// ca truststore
KeyStore ts = KeyStore.getInstance("JKS");
ts.load(new FileInputStream(trustStore), trustStorePassword.toCharArray());

// key managers
var kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, keystorePassword.toCharArray());
KeyManager[] kms = kmf.getKeyManagers();

// trust managers
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(ts);
TrustManager[] tms = tmf.getTrustManagers();

TLSClientParameters param = new TLSClientParameters();
param.setSecureSocketProtocol("TLSv1.2");
param.setDisableCNCheck(false);
param.setTrustManagers(tms);
param.setKeyManagers(kms);

// Get the client & setup the tls parameters
BindingProvider bp = (BindingProvider) port;
var client = ClientProxy.getClient(bp);

HTTPConduit https = (HTTPConduit)client.getConduit();
https.setTlsClientParameters(param);

这是我创建证书的方式。我的 java 版本是 azul zulu openjdk 11.

# Create the CA Authority that both the client and server can trust
openssl req -new -x509 -nodes -days 365 -subj '/CN=my-ca' -keyout ca.key -out ca.crt
 
# Create the server's key, certificate signing request, and certificate
openssl genrsa -out server.key 2048
openssl req -new -key server.key -subj '/CN=localhost' -out server.csr
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 365 -out server.crt
 
# Create the client's key, certificate signing request, and certificate
openssl genrsa -out client.key 2048
openssl req -new -key client.key -subj '/CN=my-client' -out client.csr
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 365 -out client.crt
openssl x509 --in client.crt -text --noout
 
# Create the root truststore
keytool -import -alias my-ca -file ca.crt -keystore truststore.jks
 
# Create pkcs12 file for key and cert chain
openssl pkcs12 -export -name server-tls -in server.crt -inkey server.key -out server.p12
 
# Create JKS for server
keytool -importkeystore -destkeystore server-keystore.jks -srckeystore server.p12 -srcstoretype pkcs12 -alias server-tls

# Create pkcs12 file for key and cert chain
openssl pkcs12 -export -name client-tls -in client.crt -inkey client.key -out client.p12

# Create JKS for client
keytool -importkeystore -destkeystore client-keystore.jks -srckeystore client.p12 -srcstoretype pkcs12 -alias client-tls

我为服务器和客户端设置了 -Djavax.net.debug=ssl,handshake,data 调试。

当我使用CXF客户端向服务器发出请求时,它发起了双向tls握手,但是服务器失败Fatal (BAD_CERTIFICATE): Empty server certificate chain,客户端失败Fatal (HANDSHAKE_FAILURE): Couldn't kickstart handshaking...readHandshakeRecord,因为它确实确实在手前发送了一个空的证书列表。

Produced client Certificate handshake message (
"Certificates": <empty list>
)

我尝试了很多不同的方法,但我似乎无法让客户端工作。

更新 出于好奇,我 运行 来自 CXF 存储库的 ws-security sample,并在示例中使用了我的 ca 证书、客户端和服务器证书。这行得通,并且它是通过 xml bean 配置的。我用我的本地尝试了同样的事情,但仍然失败。

演示应用程序和我的客户端之间的区别在于,当它查找 x.509 RSA 证书时,我的客户端失败了,但在演示应用程序中成功了。我的配置基本一样。

javax.net.ssl|ALL|01|main|2021-07-02 14:17:32.039 EDT|X509Authentication.java:213|No X.509 cert selected for EC
javax.net.ssl|WARNING|01|main|2021-07-02 14:17:32.040 EDT|CertificateMessage.java:1066|Unavailable authentication scheme: ecdsa_secp256r1_sha256
javax.net.ssl|ALL|01|main|2021-07-02 14:17:32.040 EDT|X509Authentication.java:213|No X.509 cert selected for EC
javax.net.ssl|WARNING|01|main|2021-07-02 14:17:32.040 EDT|CertificateMessage.java:1066|Unavailable authentication scheme: ecdsa_secp384r1_sha384
javax.net.ssl|ALL|01|main|2021-07-02 14:17:32.040 EDT|X509Authentication.java:213|No X.509 cert selected for EC
javax.net.ssl|WARNING|01|main|2021-07-02 14:17:32.040 EDT|CertificateMessage.java:1066|Unavailable authentication scheme: ecdsa_secp521r1_sha512
javax.net.ssl|ALL|01|main|2021-07-02 14:17:32.040 EDT|X509Authentication.java:213|No X.509 cert selected for RSA
javax.net.ssl|WARNING|01|main|2021-07-02 14:17:32.040 EDT|CertificateMessage.java:1066|Unavailable authentication scheme: rsa_pss_rsae_sha256

使用演示应用程序时不会出现最后一个错误,而是 returns 支持证书。

对于偶然发现这个问题的人,我是这样解决的。

一旦我开始使用 CXF 演示代码,我就能够将其简化为仅包含最少的依赖项和配置集。从那里我能够弄清楚这是我项目中缺少依赖项的问题。

首先,我们在服务器端使用 dropwizard,并且依赖于 dropwizard-jaxws,它引入了 cxf 依赖项。通过削减所有层,我发现演示应用程序仅在 cxf-rt-transports-http-jetty 在依赖项列表中时才有效。

dropwizard-jaxws 包含的传递依赖项是:

cxf-rt-frontend-jaxws
cxf-rt-transports-http

我还依赖于客户端中的所有 dropwizard-core,它可能实现了 cxf-rt-transports-http-jetty 实现的某些 SPI 接口(推测)。一旦我简化了依赖关系并包含了一个缺失的依赖关系,我就有了一个可重复的工作解决方案。