How to setEnabledCipherSuites when using Apache HTTP Client? - java

Since I need to work with some legacy server, and since RC4 was removed from the Java 8, I need to re-enable some RC4 based ciphers. As described in the release note we have to use SSLSocket/SSLEngine.setEnabledCipherSuites(). Since I'm using Apache HTTP Client I was not able to find a way to do this. Thanks in advance! (I also found quite semitrailer problem with out an answer so thought of posting a new one)

I was facing the same problem and I was able to figure this out.
SecureProtocolSocketFactoryImpl protFactory = new SecureProtocolSocketFactoryImpl();
httpsClient.getHostConfiguration().setHost(host, port, httpsProtocol);
In the "SecureProtocolSocketFactoryImpl" class you have to override the method public Socket createSocket() for SecureProtocolSocketFactory class.
In that method you will get a socket like this
SSLSocket soc = (SSLSocket) getSSLContext().getSocketFactory().createSocket(
socket,
host,
port,
autoClose
);
So there you will be able to do something like below.
ciphersToBeEnabled[0] = "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA";
soc.setEnabledCipherSuites(ciphersToBeEnabled);
hope you get the idea. If you have any problems please comment below. Note that doing this only will not enable RC4 related ciphers. You will need to modify java "java.security" file in jre/lib/security/ file and remove CR4 form the disabled algorithm list.

The recommended way to get the HttpClient is by using HttpClientBuilder. In this builder, you can set the HttpClientConnectionManager which in turn can take a Registry<ConnectionSocketFactory>. In this ConnectionSocketFactory, you can configure ciphers and protocols that the client want to restrict.
Sample Code:
Registry<ConnectionSocketFactory> socketFactoryRegistry;
{
SSLContext sslcontext = <your SSLContext>;
socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", new PlainConnectionSocketFactory())
.register("https", new SSLConnectionSocketFactory(sslcontext,
<your supported protocols, could be null>,
<your supported ciphers, could be null>,
<your HostnameVerifier>
.build();
}
HttpClientBuilder b = HttpClientBuilder.create()
.setConnectionManager(new BasicHttpClientConnectionManager(socketFactoryRegistry))
.set<anything else you want>(<with what you want>);
HttpClient client = b.build();

Related

apache httpclient: AEADBadTagException: Tag mismatch

Seldomly, I'm getting this exception:
javax.crypto.AEADBadTagException: Tag mismatch!
That's my http client configuration:
SSLContext sslContext = SSLContextBuilder.create()
.loadTrustMaterial(
new URL(visorApiProperties.getTrustStore()),
visorApiProperties.getTrustStorePassword().toCharArray()
)
.build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
.register("https", sslsf)
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.build();
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
httpClientBuilder.setConnectionManager(cm);
I don't quite figure out what does it mean.
I've took a look on other stackoverflow questions, but I don't quite figure out what would I do in order to solve this problem.
Any ideas?
For me problem was in sertificate.
If you try execute this curl result will be the same.
curl -I -vv --cert sertificate_location --cert-type p12 --pass password url
OpenSSL SSL_read: error:0A000119:SSL routines::decryption failed or bad record mac, errno 0
The exception javax.crypto.AEADBadTagException means that the data could not be encoded and decoded using authenticated encryption with additional data (AEAD). "Tag mismatch!" indicates that the tag value (the value added to the encrypted data to allow for data integrity verification after decryption) is inconsistent between the sending and receiving sides.
To solve this problem, the following should be verified:
Is the encryption key the same on both sides?
Are there any issues with the trust certificates in the configured trust store?
Is the version of the javax.crypto library compatible on both sides?
Are the sending side and receiving side using the same encryption mode and encryption settings?

Java HttpClient with TLS/SSLContext through proxy

I want to make a HTTP call to server that uses TLS to authenticate. Moreover server needs my IP to be whitelisted, so with AWS Lambda I need to use proxy. What I want to achieve is HTTP POST request with TLS that goes through proxy.
To achieve TLS protocol I use KeyStore with loaded certs and private key.
Making a call without proxy (locally from whitelisted IP) works, so I assume keyStore is configured correctly.
Here is how I build httpClient (it's java.net.http.HttpClient):
var keyManagerFactory = KeyManagerFactory.getInstance("PKIX");
keyManagerFactory.init(keyStore, null);
var trustManagerFactory = TrustManagerFactory.getInstance("PKIX");
trustManagerFactory.init(keyStore, null);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
URI proxyUri = config.getProxyUri(); // this is injected object with preloaded config parameters
HttpClient httpClient = HttpClient.newBuilder()
.sslContext(sslContext)
.proxy(
ProxySelector.of(
InetSocketAddress.createUnresolved(proxyUri.getHost(), proxyUri.getPort())))
.build();
Now making a request:
String body = createRequestBody(); // creates string with JSON
HttpRequest request = HttpRequest.newBuilder()
.uri(config.getServiceUri()) // same config as in example above
.header("Content-Type", "application/json")
.POST(BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = httpClient.send(request, BodyHandlers.ofString());
Calling .send(...) causes
java.io.IOException: Tunnel failed, got: 403
# java.net.http/jdk.internal.net.http.HttpClientImpl.send(Unknown Source)
# java.net.http/jdk.internal.net.http.HttpClientFacade.send(Unknown Source)
# (method we are in above example)
Proxy doesn't need any authentication and in other AWS Lambda I've seen this proxy working with builder using only .proxy(...) method just like in the example above. So the only thing that is different is this .sslContext(...).
Do I need some more sslContext configuration? I've been searching for some examples with TLS through proxy, but I've not managed to find anything.
HttpClient.Builder Docs doesn't say anything about proxy with sslContext either.
Thanks for help!
As daniel wrote in a comment
It would seem that you have insufficient permission to access the service you're trying to use
It turned out to be proxy config that was blocking traffic to that specific host and port.
There is nothing wrong with the code above. After a change in proxy settings to it works as expected.
Thanks for help!

ES 7.4.1 - Authentication [Rest API]

I’m a newbie in ES and I have a task in my new job to upgrade from 6.4.2 to 7.4.1 – From TCP client to Rest High Level API.
Previously we built the client like this:
Settings settings = Settings.builder()
.put("xpack.security.user", String.format("%s:%s",esJavaUser,esJavaPassword))
.put("cluster.name", esClusterName)
.put("xpack.security.transport.ssl.enabled", xpackSecurityTransportSslEnabled)
.put("xpack.ssl.certificate_authorities", xpackSslCertificateAuthorities)
.build();
client = new PreBuiltXPackTransportClient(settings);
Now, in rest API, it’s changed to this:
final CredentialsProvider credentialsProvider =
new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(esJavaUser, esJavaPassword));
RestClientBuilder restClientBuilder = RestClient.builder(hosts)
.setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder
.setDefaultCredentialsProvider(credentialsProvider));
restHighLevelClient = new RestHighLevelClient(restClientBuilder);
With this build I set ES user and password by CredentialsProvider but what about ssl.enabled and certificate_authorities”? how should I provided them with rest API?
I got an answer from ES forum (didn't thought to ask there first..)
Because, as developer, I always looking for answer here, in stackoverflow, I decide to not delete this question and copy TimV answer:
The documentation you are looking for is here: https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.4/_encrypted_communication.html
SSL is automatically enabled (or not) based on the scheme (protocol) in the HttpHost objects you pass to the builder.
RestClient.builder(hosts)
If you are using SSL, you want to pass "https" as the scheme (3rd argument) when you construct the HttpHost objects (hosts).
Unfortunately there is no simple means to pass certificate_authorities to the Rest client, you need to turn those certificates into a standard Java truststore.
You can probably find some sample code on the web ("convert PEM certificates to Java truststore"), but the gist of it is:
Open the certificate authority files as an InputStream
Create a X.509 certificate factory: java.security.cert.CertificateFactory.getInstance("X.509")
Call generateCertificates on the certificate factory to read those certificate files into java Certificate objects
Construct an empty KeyStore object
Add the loaded certificates as trusted entries
Pass that to SSLContextBuilder.loadTrustMaterial
Link: https://discuss.elastic.co/t/es-7-4-1-authentication-rest-api/211969

CometD: Use SSL/TLS

How do I enable secure connections with CometD?
I have an app that is working when I use an "http" protocol for the BayeuxServer. If I switch to "https", I get failed handshakes.
What is the correct way to use a secure connection in CometD?
This is via the Java Client.
Here is the error:
{failure={exception=java.lang.NullPointerException, message={ext={ack=true}, supportedConnectionTypes=[long-polling], channel=/meta/handshake, id=4, version=1.0}, connectionType=long-polling}, channel=/meta/handshake, id=4, subscription=null, successful=false}
I do not see any exceptions on the server (ie, the null pointer is not in our code), and if I use HTTP, it works fine.
I've pieced together the following for the Java client side:
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setTrustAll(true); // only interacting with our backend, so accept self-signed certs
WebSocketClient webSocketClient = new WebSocketClient(sslContextFactory);
webSocketClient.start();
ClientTransport wsTransport = new JettyWebSocketTransport(null, null, webSocketClient);
HttpClient httpClient = new HttpClient(sslContextFactory);
httpClient.start();
ClientTransport httpTransport = new LongPollingTransport(null, httpClient);
I believe that will do it.
I still need to figure out how to configure the server side cometd to accept the secure connections. I am using the Spring setup.
The answer to the server side is: Its a pain in the ass.
Here is how you can get it working with the jetty maven plugin:
http://juplo.de/configure-https-for-jetty-maven-plugin-9-0-x/#comment-53352

SSL and SocketChannel

Ideally, I only need a simple SSLSocketChannel.
I already have a component that reads and writes message over ordinary SocketChannel, but for some of these connections, I have to use SSL over the wire; the operations over these connections, however, are the same.
Does anyone knows a free SSLSocketChannel implementation (with the appropriate selector) or something similar? I've found this, but the selector doesn't accept it since its vendor isn't SUN.
I'm decoupling the reading_from/writing_to net logic from the insertion and retrieval of network data via a simple object, in order to use a SSLEngine without getting mad, but it's really tricky to implement that correctly, given the fact that I don't know the internals of SSL protocol...
Jetty has an NIO SSL implementation for their server: SslSelectorChannelConnector. You might want to peek at it for details on what its doing.
There is also an old (but decent) article from O'Reilly that explains the details about NIO + SSL along with example code.
TLS Channel is a simple library that does exactly that: wrapping a SSLContext (or SSLEngine) and exposing a ByteChannel interface, doing the heavy lifting internally.
(Disclaimer: I am the library's main author).
Check out Restlet's implementation it may do what you need, and it's all about NIO.
Restlet Engine Javadoc
Specifically the HttpClientCall. SetProtocol(HTTPS) - getResponseEntityChannel returns a ReadableByteChannel (getEntityChannel returns a WriteableByteChannel)
Not sure if this is what you're looking for, but may help... To create SSL/TLS enabled server sockets, I'm currently using code like the following (keystore.jks contains a self signed private/public key pair used for securing confirmation) - clients have a similar trust store which contains the signed certificate with the public key of that pair.
A bit of googling around getting that configured should get you underway.
String keyStorePath = "keystore.jks";
String keyStorePassword = "password";
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyStore keyStore = new KeyStore();
keyStore.load(new FileInputStream(keyStorePath), keyStorePassword);
keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());
SSLContext sslContext = getServerSSLContext(namespace.getUuid());
SSLServerSocketFactory serverSocketFactory = sslContext.getServerSocketFactory();
// Create sockets as necessary

Categories

Resources