Is there any way to setup a separate SSL Context Factory for each route in Apache HTTP Client. From the documentation i can only see we can configure SSLContextFactory per scheme not per route.
Registry<ConnectionSocketFactory> r = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", plainsf)
.register("https", sslsf)
.build();
HttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(r);
HttpClients.custom()
.setConnectionManager(cm)
.build();
All my target endpoints are protected with HTTPS and mandates client certificate authentication. For each of these endpoint i have to select a specific client certificate & present it to the endpoint server. Currently i am seeing the only way is to create a separate HTTPClient instance for each target endpoint route & configure with SSLContext Factory instance.
I am here looking for any pointers to improving this design.
Thanks.
I have posted this question in Apache HTTP Client Forums & got the solution on how to handle this.
One can use 'http.socket-factory-registry' context attribute to that
end but please note this feature is considered undocumented.
http://hc.apache.org/httpcomponents-client-4.5.x/httpclient/xref/org/apache/http/impl/conn/DefaultHttpClientConnectionOperator.html#66
Please also note that one can build a custom ConnectionSocketFactory
that makes use of custom context attributes when creating SSL sockets.
That would be the recommended way to solve the issue.
Related
Small question regarding the Spring WebClient from Spring Webflux, and how to configure the TLS versions when sending outbound http request (where I'm the client).
In a SpringBoot MVC project (not Webflux), I am using the Webflux Webclient (which is possible to mix the two).
I am using the client to call two external parties HTTP servers I have absolutely no control over.
First service is AliceService. Alice service is configured to accept only requests that are TLSv1.2
Second service is BobService. Bob Service is configured the accept only requests that are TLSV1.3
Hence, I am currently facing an issue. I am never able to call both of them at the same time.
I tried different combinations of
-Djdk.tls.client.protocols=TLSv1.2
-Djdk.tls.client.protocols=TLSv1.3
server.ssl.enabled-protocols=TLSv1.2
server.ssl.enabled-protocols=TLSv1.3
Every time, I will either change all my outbound requests to TLSv1.2 (hence the service accepting only TLSv1.3 will fail) or change all my outbound call to TLSv1.3 (hence the service accepting only TLSv1.2 will fail).
I even have two different instances of WebClient.
May I ask how to configure which TLS version is used when sending the outbound request?
How to resolve this problem please?
Thank you
You can create two separate web clients for this purpose. One for TLSv1.2 and one for TLSv1.3.
Here is the minimum code snippet that you can use to build your WebClient along with other configuration that you need.
WebClient for TLSv1.2
final SslContext sslContextForTls12 = SslContextBuilder.forClient()
.protocols("TLSv1.2")
.build();
final HttpClient httpClientForTls12 = HttpClient.create()
.secure(ssl -> ssl.sslContext(sslContextForTls12));
final WebClient webClientForTls12 = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClientForTls12))
.build();
WebClient for TLSv1.3
final SslContext sslContextForTls13 = SslContextBuilder.forClient()
.protocols("TLSv1.3")
.build();
final HttpClient httpClientForTls13 = HttpClient.create()
.secure(ssl -> ssl.sslContext(sslContextForTls13));
final WebClient webClientForTls13 = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClientForTls13))
.build();
I have tested this configuration for a website that supports various TLS versions.
This answer has mentioned the list of sites that I used for testing.
This should not be hardcoded/-configured but auto negotiated during TLS handshake, based on the capabilities of server and client, otherwise it becomes a maintenance nightmare. You do not need to worry about downgrade attacks as TLS has a protection mechanism to detect that.
You should be able to see this with: -Djavax.net.debug=all or similar. See the JSSE Reference Guide, Debugging Utilities
If you want to avoid certain older versions of TLS the correct solution is to blacklist insecure old ciphers (due to security/compliance) which can be accomplished through changing: jdk.tls.disabledAlgorithms in the config file: jre/lib/security/java.security, which on up-to-date Java installations contains reasonably secure defaults.
I am using Jetty HTTP2 Client 9.4.12 to support HTTP/2 server connection. It's working fine but on top of verifying the certificate, I also want to verify the Hostname using my javax.net.ssl.HostnameVerifier implementation. According to the doc https://www.eclipse.org/jetty/javadoc/9.4.12.v20180830/org/eclipse/jetty/util/ssl/SslContextFactory.html I can use setEndpointIdentificationAlgorithm("HTTPS") to enable hostname verification but how do I configure Jetty to use my implementation?
Thanks.
Jetty's HttpClient does not support javax.net.ssl.HostnameVerifier.
An enhancement request exists for this ...
https://github.com/eclipse/jetty.project/issues/3154
Feel free to comment on the open enhancement requesting it, and providing an example of how you would use it.
The support has been added in Jetty v9.4.15.v20190215.
final SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setHostnameVerifier((hostname, sslSession) -> {
// logic to verify hostname
return false;
});
My project use xfire as a web service client api. My project is in legacy Servlet/JSP. We used XFire eclipse plugin to generate client stub.
Web-service has Migrated to SLL (HTTPS). Is there any easy way to consume Webservice over SSL in XFire.
I found some code at http://docs.codehaus.org/display/XFIRE/HTTP+Transport.
I have some confusion there too. It motivates to use not-so-common-ssl which is in Alpha and I don't know if it is stable enough to be used in production.
// Technique similar to http://juliusdavies.ca/commons- ssl/TrustExample.java.html
HttpSecureProtocol protocolSocketFactory = new HttpSecureProtocol();
// "/thecertificate.cer" can be PEM or DER (raw ASN.1). Can even be several PEM certificates in one file.
TrustMaterial trustMaterial = new TrustMaterial(getClass().getResource("/thecertificate.cer"));
// We can use setTrustMaterial() instead of addTrustMaterial() if we want to remove
// HttpSecureProtocol's default trust of TrustMaterial.CACERTS.
protocolSocketFactory.addTrustMaterial(trustMaterial);
// Maybe we want to turn off CN validation (not recommended!):
protocolSocketFactory.setCheckHostname(false);
Protocol protocol = new Protocol("https", (ProtocolSocketFactory) protocolSocketFactory, 8443);
Protocol.registerProtocol("https", protocol);
Now above is a way to create a Protocol factory and getting it registered with Apache HTTPclient api. But id doesnot say what to do further with the generated stub.
Please feel free to ask more information if any.
We can't move to other web-service client api so that is not an option.
Managed to solve my own problem.
This is how I did it. XFire use Apache Http client internally so setting Security certifect detail on this Api will do the job. We will use no-yet-common-ssl.jar for this purpose.
First we will create org.apache.commons.ssl.TrustMaterial using commons and then set it in HttpSecureProtocol which is a child of javax.net.ssl.SSLSocketFactory.
Suppose XYZ.cer is the client certifect provided by service provider.
HttpSecureProtocol protocolSocketFactory = new HttpSecureProtocol();
protocolSocketFactory.addTrustMaterial(TrustMaterial.DEFAULT); //for trusting all the certifects in java trusted Store.
protocolSocketFactory.addTrustMaterial(new TrustMaterial(getClass().getResource("/XYZ.cer")));
Protocol protocol = new Protocol("https", (ProtocolSocketFactory)protocolSocketFactory, 443);
Protocol.registerProtocol("https", protocol);
If this is a web Application you can do this in ServletContextListener or in any part of code that executes when application boots.
Now you can use any ssl service using Xfire client stub. Any service which implement the above certifect.
Now why this work. Because XFire uses Apache Http Client as a connection api and we are telling Http client to use the above TrustManager when HTTPS is used.
I am writing a routine to access a remote server. This server I am connecting to requires mutual authentication so I have to provide a keystore, and while I'm at it I'd like to put a proper truststore in place as well.
I can find plenty of tutorials on how to create a keystore with keytool and multiple ways to get an Apache HTTP client to recognize it, but not where to store it in a Tomcat environment so that the application can find it. Somehow putting it in the application's war file seems like a bad idea to me.
Again, this is not to permit Tomcat to handle inbound https connections - I have a reverse proxy set up by our admin team for that. I'm creating outgoing https connections that require mutual authentication, i.e., both accepting a self-signed destination server certificate, and providing my server's self-signed client certificate.
Where do you store the actual keystore and truststore files in a Tomcat environment for use by a web application?
You can put your keystore wherever you want, as long as you know how to tell httpclient where to load the keystore.
That, of course, is the trick.
Using Apache httpclient for https
Buried in all that mess of code in the accepted answer is the key (ha!) to using httpclient with your own custom keystore. It's unfortunate that httpclient doesn't have a simple API like "here's the path to my keystore file, now use it" or "here are the bytes for my keystore, use those" (if you wanted to load the keystore from the ClassLoader or whatever), but that seems to be the case.
The honest truth is that using keystores and truststores in Java is messy business, and there's usually no way around it. Having written a client-cert-capable HTTP client myself using nothing other than HttpsURLConnection and then also adding raw-socket components to that, I know how painful it is.
The code in the above-linked article is fairly straightforward if a bit verbose. Unfortunately, you're going to need to make it a lot messier for production-quality code because you've got to do error-checking, etc. for every step of the process to make sure your service doesn't fall-over when you are trying to set up the various stores and make your connection.
This is basically a comment to Christopher Schultz's answer, but since it involves some code snippets please excuse my putting it here
It's unfortunate that httpclient doesn't have a simple API like
"here's the path to my keystore file, now use it" or "here are the
bytes for my keystore, use those" (if you wanted to load the keystore
from the ClassLoader or whatever), but that seems to be the case
This is how one can configure Apache HttpClient 4.3 to use a specific trust store for SSL context initialization.
SSLContext sslContext = SSLContexts.custom()
.loadTrustMaterial(trustStore)
.build();
CloseableHttpClient client = HttpClients.custom()
.setSslcontext(sslContext)
.build();
One can load trust material from a resource like that
URL resource = getClass().getResource("/com/mycompany/mystuff/my.truststore");
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream inputStream = resource.openStream();
try {
trustStore.load(inputStream, null /*usually not password protected*/);
} finally {
inputStream.close();
}
I'm making a request using Apache's DefaultHttpClient and I need to override the SSL call so that I can attach a custom SSLSocketFactory (essentially a client cert) to it. To do this, I create a new custom scheme and connection manager and pass them over at httpClient creation:
...
new Scheme("https", 443, thirdPartySocketFactory);
...
DefaultHttpClient httpClient = new DefaultHttpClient(ccm);
...
The thirdPartySocketFactory passed at Scheme creation is the important part here. As the name suggests, it comes from a third party library that automatically handles mutual cert authentication with a JBoss server. I.e., it attaches a client cert + password to each outbound request.
The call itself is also made from a JBoss server (I'm actually hosting SOLR on it which is why the Apache call is necessary in the first place).
Problem is, the thirdPartySocketFactory object is of the type javax.net.SocketFactory, not org.apache.http.conn.scheme.SocketFactory which the Apache Scheme requires (see here).
Question: Is there any way to solve this nicely via some sort of wrapper? I thought the Apache SocketFactory would inherit from the javax.net one in some way but it doesn't look like it.
Alternatively, I can create a truely custom Apache SSLSocketFactory (see here), complete with truststore etc, but I really don't want to go down route until the other option is exhausted. Main reason being, the thirdPartySocketFactory method is tried and tested.
Thanks!
There is SSLSocketFactory#SSLSocketFactory(javax.net.ssl.SSLSocketFactory socketfactory, X509HostnameVerifier hostnameVerifier) since version 4.2. This constructor serves as a adapter for any arbitrary JSSE SSLSocketFactory implementation.