问题
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 benull
.Returns:
an updated client builder instance.
Throws:
NullPointerException
- in case thesslContext
parameter isnull
.
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 referencingHttpClient
, but for some applications it may be necessary to drill down into theHttpClient
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