Is it possible to reuse Java trusted certificates for javax.ws.rs (Resteasy implementation)?

左心房为你撑大大i 提交于 2019-12-21 09:35:02

问题


Suppose we need to trust a self-signed SSL certificate. As an example, let's use https://self-signed.badssl.com/.

Since the signer is not a "proper" authority, Java doesn't trust it and refuses to connect to that server. However, after

$ cd $JAVA_HOME/jre/lib/security
$ keytool -import -trustcacerts -alias ... -file ... -keystore cacerts

and restart of the application, the following code works:

new URL ("https://self-signed.badssl.com/").openConnection ().getResponseCode ()

and returns 200 (OK), without throwing an exception. I.e. basic Java way of opening an HTTPS connection now works, since the certificate is now trusted.

However, this doesn't have any visible effect on javax.ws.rs Client (as implemented in Resteasy, at least) and I still get an exception:

javax.ws.rs.ProcessingException: Unable to invoke request
        at org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine.invoke(ApacheHttpClient4Engine.java:287)
        at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.invoke(ClientInvocation.java:407)
        at org.jboss.resteasy.client.jaxrs.internal.ClientInvocationBuilder.method(ClientInvocationBuilder.java:273)
        [...]
Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
        at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1949)
        at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:302)
        at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:296)
        at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1506)
        at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216)
        at sun.security.ssl.Handshaker.processLoop(Handshaker.java:979)
        at sun.security.ssl.Handshaker.process_record(Handshaker.java:914)
        at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1062)
        at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1375)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1403)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1387)
        at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:535)
        at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:403)
        at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:177)
        at org.apache.http.impl.conn.ManagedClientConnectionImpl.open(ManagedClientConnectionImpl.java:304)
        at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:611)
        at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:446)
        at org.apache.http.impl.client.AbstractHttpClient.doExecute(AbstractHttpClient.java:863)
        at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
        at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:57)
        at org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine.invoke(ApacheHttpClient4Engine.java:283)
        ... 90 more
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:387)
        at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292)
        at sun.security.validator.Validator.validate(Validator.java:260)
        at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
        at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:229)
        at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
        at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1488)
        ... 107 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:146)
        at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:131)
        at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
        at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:382)
        ... 113 more

It seems as Resteasy doesn't take "standard" keystore into account. But I would rather like to have a central (machine-specific) place for additional trusted keys and not bother how exactly application uses them, with URL.openConnection or javax.ws.rs.

Question Is it possible to make javax.ws.rs Client use the same keystore as "normal" Java HTTPS connection mechanism?


回答1:


Setting the SSL context when creating the Client instance

In the ClientBuilder API there's a method that allows you to set the SSLContext:

public abstract ClientBuilder sslContext(SSLContext sslContext)

Set the SSL context that will be used when creating secured transport connections to server endpoints from web targets created by the client instance that is using this SSL context. The SSL context is expected to have all the security infrastructure initialized, including the key and trust managers.

Setting a SSL context instance resets any key store or trust store values previously specified.

Parameters:

sslContext - secure socket protocol implementation which acts as a factory for secure socket factories or SSL engines. Must not be null.

Returns:

an updated client builder instance.

Throws:

NullPointerException - in case the sslContext parameter is null.

Assuming you have added the certificate to cacerts trust store, you could use the default SSLContext when creating your Client instance.

Client client = ClientBuilder.newBuilder().sslContext(SSLContext.getDefault()).build();

It should be enough. However, for some reason, the above piece of code does not work with RESTEasy, but does work with Jersey. It's very likely it's a RESTEasy bug.

The standard solution does not work with RESTEasy. What should I do?

The RESTEasy documentation states the following:

Network communication between the client and server is handled in RESTEasy, by default, by HttpClient (4.x) from the Apache HttpComponents project. [...]

RESTEasy and HttpClient make reasonable default decisions so that it is possible to use the client framework without ever referencing HttpClient, but for some applications it may be necessary to drill down into the HttpClient details. [...]

To customize the HttpClient used by RESTEeasy, do the following:

HttpClient httpClient = HttpClientBuilder.create()
                                         .setSslcontext(SSLContext.getDefault())
                                         .build();

ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(httpClient);
Client client = new ResteasyClientBuilder().httpEngine(engine).build();

Then you can perform the request:

Response response = client.target("https://self-signed.badssl.com/").request().get();
System.out.println(response.getStatus());

Are there any alternatives to the SSL context?

Instead of using the SSLContext when creating your Client, you could load a KeyStore. To load cacerts trust store, you can do the following:

String filename = System.getProperty("java.home") + 
        "/lib/security/cacerts".replace('/', File.separatorChar);
FileInputStream is = new FileInputStream(filename);
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
String password = "changeit";
keystore.load(is, password.toCharArray());

The cacerts' default password is changeit.

Then create your Client instance using one of the following approaches:

Client client = ClientBuilder.newBuilder().trustStore(keystore).build();
Client client = ClientBuilder.newBuilder().keyStore(keystore, password).build();

The issue is that it doesn't work with RESTEasy, but does work with Jersey.


The solutions mentioned above were tested against the following JAX-RS Client API implementations:

  • jersey-client (version 2.23.1)
  • resteasy-client (version 3.0.18.Final)


来源:https://stackoverflow.com/questions/38505294/is-it-possible-to-reuse-java-trusted-certificates-for-javax-ws-rs-resteasy-impl

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!