Setting Minimum TLS Version Micronaut HttpClient - ssl

The documentation from Micronaut is limited here.
We're using io.micronaut.http.client.HttpClient to access a 3rd party API.
All I can find is this https://docs.micronaut.io/latest/guide/configurationreference.html but it doesn't give examples.
How do we specify the version of TLS that we are prepared to interact with?

To specify the version of TLS that you are prepared to interact with for io.micronaut.http.client.HttpClient, you can use the sslContext property of the HttpClientConfiguration class.
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.annotation.Value;
import io.micronaut.http.client.HttpClientConfiguration;
import io.micronaut.http.client.HttpClientFactory;
import io.micronaut.http.client.annotation.Client;
import javax.inject.Singleton;
import javax.net.ssl.SSLContext;
import java.security.NoSuchAlgorithmException;
#Factory
public class MyHttpClientFactory {
#Value("${my.http.client.url}")
String url;
#Singleton
#Client("myClient")
HttpClientConfiguration myClientConfig(HttpClientFactory factory) throws NoSuchAlgorithmException {
HttpClientConfiguration clientConfig = factory.createConfiguration(url);
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, null, null);
clientConfig.setSslContext(sslContext);
return clientConfig;
}
}
In this code, we create a factory class named MyHttpClientFactory that defines a myClientConfig method. This method creates a new HttpClientConfiguration instance and sets its sslContext property to use TLS version 1.2.
You can then use this factory class to create instances of io.micronaut.http.client.HttpClient that are prepared to interact with servers using TLS version 1.2.

Related

Upgrading From ActiveMQ "Classic" to ActiveMQ Artemis - class java.util.ArrayList is not a valid property type

I am upgrading ActiveMQ "Classic" to ActiveMQ Artemis while maintaining client code. I have multiple places code looking like this.
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jms.core.JmsTemplate;
public class TestV {
public static void main(String[] args) throws IOException {
ApplicationContext ctx = new ClassPathXmlApplicationContext("root.xml");
JmsTemplate jms = ctx.getBean(JmsTemplate.class);
Map<String, Object> map = new HashMap<>();
List<Integer> ids = new ArrayList<>();
ids.add(10);
ids.add(20);
map.put("ids", ids);
map.put("updated", true);
jms.convertAndSend("mytest", map);
}
}
How do I fix below error coming from above code.
Exception in thread "main" org.springframework.jms.UncategorizedJmsException: Uncategorized exception occurred during JMS processing; nested exception is javax.jms.JMSException: org.apache.activemq.artemis.api.core.ActiveMQPropertyConversionException: class java.util.ArrayList is not a valid property type
at org.springframework.jms.support.JmsUtils.convertJmsAccessException(JmsUtils.java:311)
at org.springframework.jms.support.JmsAccessor.convertJmsAccessException(JmsAccessor.java:185)
at org.springframework.jms.core.JmsTemplate.execute(JmsTemplate.java:507)
at org.springframework.jms.core.JmsTemplate.send(JmsTemplate.java:584)
at org.springframework.jms.core.JmsTemplate.convertAndSend(JmsTemplate.java:661)
at com.mycompany.adhoc.TestV.main(TestV.java:33)
Caused by: javax.jms.JMSException: org.apache.activemq.artemis.api.core.ActiveMQPropertyConversionException: class java.util.ArrayList is not a valid property type
at org.apache.activemq.util.JMSExceptionSupport.create(JMSExceptionSupport.java:54)
at org.apache.activemq.ActiveMQConnection.syncSendPacket(ActiveMQConnection.java:1404)
at org.apache.activemq.ActiveMQConnection.syncSendPacket(ActiveMQConnection.java:1437)
at org.apache.activemq.ActiveMQConnection.syncSendPacket(ActiveMQConnection.java:1324)
at org.apache.activemq.ActiveMQSession.send(ActiveMQSession.java:1981)
at org.apache.activemq.ActiveMQMessageProducer.send(ActiveMQMessageProducer.java:288)
at org.apache.activemq.ActiveMQMessageProducer.send(ActiveMQMessageProducer.java:223)
at org.apache.activemq.ActiveMQMessageProducerSupport.send(ActiveMQMessageProducerSupport.java:241)
at org.springframework.jms.core.JmsTemplate.doSend(JmsTemplate.java:634)
at org.springframework.jms.core.JmsTemplate.doSend(JmsTemplate.java:608)
at org.springframework.jms.core.JmsTemplate.lambda$send$3(JmsTemplate.java:586)
at org.springframework.jms.core.JmsTemplate.execute(JmsTemplate.java:504)
In ActiveMQ "Classic" we can set trusted packages in connection factory. How do I do it in ActiveMQ Artemis?
This problem has nothing to do with setting the trusted packages on the JMS ConnectionFactory.
The problem is that your application is implicitly using this JMS "extension" provided by ActiveMQ "Classic." As the documentation states:
This JMS extension feature allows you to attach Map and List properties to any JMS Message or to use nested Maps and Lists inside a MapMessage. [emphasis mine]
When you pass the Map<String, Object> variable map to JmsTemplate.convertAndSend it uses the default SimpleMessageConverter to convert that Map into a javax.jms.MapMessage. As the JavaDoc for MapMessage states:
The names are String objects, and the values are primitive data types in the Java programming language. [emphasis mine]
In other words, according to the JMS specification the values in the MapMessage can only be primitive data types. However, ActiveMQ "Classic" provides an extension which allows using List implementations. Code which uses this extension is not portable to other JMS brokers since it does not adhere to the JMS specification. This is why ActiveMQ Artemis throws the error ActiveMQPropertyConversionException: class java.util.ArrayList is not a valid property type.
You will either have to change your application code to adhere to the JMS specification (i.e. use primitive data types in the values of your Map) or this same extension will have to be implemented by ActiveMQ Artemis.

Jackson annotations quarkus resteasy client

I have a client package where I have defined my REST clients, containing the following interface and models:
#Path("/")
#RegisterRestClient(configKey = "some-api")
public interface SomeRestClient {
#POST
#Path("/oauth/token")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
OAuthResult getOAuthToken(OAuthRequest oAuthRequest);
}
and OAuthRequest class:
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#AllArgsConstructor
#NoArgsConstructor
public class OAuthRequest {
private String email;
private String password;
#JsonProperty("grant_type")
private String grantType;
#JsonProperty("client_id")
private String clientId;
#JsonProperty("client_secret")
private String clientSecret;
}
I import this package into my main service package, but quarkus does not seem to pick up the Jackson annotations. The properties of the request are serialized using camel case, when they should be in snake case.
#Inject
#RestClient
private SomeRestClient someRestClient;
OAuthRequest oAuthRequest = new OAuthRequest();
//set fields
OAuthResult oAuthResult = someRestClient.getOAuthToken(oAuthRequest);
EDIT:
I am using the quarkus-rest-client-jackson and the quarkus-rest-client dependencies, no jsonb dependency anywhere.
I have tried to narrow the problem down: I have moved the client / request classes to my main package, and I have removed the lombok annotations and made my fields which have Jackson annotations public. Still the same problem...
Could anyone point me in the right direction of what I am doing wrong?
Reasteasy is used for your reste endpoint not to access a remote REST service.
To access a remote REST service the REST client is used.
The REST client comes with Jackson support of you use the quarkus-rest-client-jackson dependency not the JSON-B one.

How to use Apache Apex Malhar RabbitMQ operator in DAG

I have an Apache Apex application DAG which reads RabbitMQ message from a queue. Which Apache Apex Malhar operator should I use? There are several operators but it's not clear which one to use and how to use it.
Have you looked at https://github.com/apache/apex-malhar/tree/master/contrib/src/main/java/com/datatorrent/contrib/rabbitmq ? There are also tests in https://github.com/apache/apex-malhar/tree/master/contrib/src/test/java/com/datatorrent/contrib/rabbitmq that show how to use the operator
https://github.com/apache/apex-malhar/blob/master/contrib/src/main/java/com/datatorrent/contrib/rabbitmq/AbstractRabbitMQInputOperator.java
That is the main operator code where the tuple type is a generic parameter and emitTuple() is an abstract method that subclasses need to implement.
AbstractSinglePortRabbitMQInputOperator is a simple subclass that provides a single output port and implements emitTuple() using another abstract method getTuple() which needs an implementation in its subclasses.
The tests that Sanjay pointed to show how to use these classes.
I also had problems finding out how to read messages from RabbitMQ to Apache Apex. With the help of the provided links of Sanjay's answer (https://stackoverflow.com/a/42210636/2350644) I finally managed to get it running. Here's how it works all together:
1. Setup a RabbitMQ Server
There are lot of ways installing RabbitMQ that are described here: https://www.rabbitmq.com/download.html
The simplest way for me was using docker (See: https://store.docker.com/images/rabbitmq)
docker pull rabbitmq
docker run -d --hostname my-rabbit --name some-rabbit -p 5672:5672 -p 15672:15672 rabbitmq:3-management
To check if RabbitMQ is working, open a browser and navigate to: http://localhost:15672/. You should see the Management page of RabbitMQ.
2. Write a Producer program
To send messages to the queue you can write a simple JAVA program like this:
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.util.ArrayList;
public class Send {
private final static String EXCHANGE = "myExchange";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE, BuiltinExchangeType.FANOUT);
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, EXCHANGE, "");
List<String> messages = Arrays.asList("Hello", "World", "!");
for (String msg : messages) {
channel.basicPublish(EXCHANGE, "", null, msg.getBytes("UTF-8"));
System.out.println(" [x] Sent '" + msg + "'");
}
channel.close();
connection.close();
}
}
If you execute the JAVA program you should see some outputs in the Management UI of RabbitMQ.
3. Implement a sample Apex Application
3.1 Bootstrap a sample apex application
Follow the official apex documentation http://docs.datatorrent.com/beginner/
3.2 Add additional dependencies to pom.xml
To use the classes provided by malhar add the following dependencies:
<dependency>
<groupId>org.apache.apex</groupId>
<artifactId>malhar-contrib</artifactId>
<version>3.7.0</version>
</dependency>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>4.2.0</version>
</dependency>
3.3 Create a Consumer
We first need to create an InputOperator that consumes messages from RabbitMQ using available code from apex-malhar.
import com.datatorrent.contrib.rabbitmq.AbstractSinglePortRabbitMQInputOperator;
public class MyRabbitMQInputOperator extends AbstractSinglePortRabbitMQInputOperator<String> {
#Override
public String getTuple(byte[] message) {
return new String(message);
}
}
You only have to override the getTuple() method. In this case we simply return the message that was received from RabbitMQ.
3.4 Setup an Apex DAG
To test the application we simply add an InputOperator (MyRabbitMQInputOperator that we implemented before) that consumes data from RabbitMQ and a ConsoleOutputOperator that prints the received messages.
import com.rabbitmq.client.BuiltinExchangeType;
import org.apache.hadoop.conf.Configuration;
import com.datatorrent.api.annotation.ApplicationAnnotation;
import com.datatorrent.api.StreamingApplication;
import com.datatorrent.api.DAG;
import com.datatorrent.api.DAG.Locality;
import com.datatorrent.lib.io.ConsoleOutputOperator;
#ApplicationAnnotation(name="MyFirstApplication")
public class Application implements StreamingApplication
{
private final static String EXCHANGE = "myExchange";
#Override
public void populateDAG(DAG dag, Configuration conf)
{
MyRabbitMQInputOperator consumer = dag.addOperator("Consumer", new MyRabbitMQInputOperator());
consumer.setHost("localhost");
consumer.setExchange(EXCHANGE);
consumer.setExchangeType(BuiltinExchangeType.FANOUT.getType());
ConsoleOutputOperator cons = dag.addOperator("console", new ConsoleOutputOperator());
dag.addStream("myStream", consumer.outputPort, cons.input).setLocality(Locality.CONTAINER_LOCAL);
}
}
3.5 Test the Application
To simply test the created application we can write a UnitTest, so there is no need to setup a Hadoop/YARN cluster.
In the bootstrap application there is already a UnitTest namely ApplicationTest.java that we can use:
import java.io.IOException;
import javax.validation.ConstraintViolationException;
import org.junit.Assert;
import org.apache.hadoop.conf.Configuration;
import org.junit.Test;
import com.datatorrent.api.LocalMode;
/**
* Test the DAG declaration in local mode.
*/
public class ApplicationTest {
#Test
public void testApplication() throws IOException, Exception {
try {
LocalMode lma = LocalMode.newInstance();
Configuration conf = new Configuration(true);
//conf.addResource(this.getClass().getResourceAsStream("/META-INF/properties.xml"));
lma.prepareDAG(new Application(), conf);
LocalMode.Controller lc = lma.getController();
lc.run(10000); // runs for 10 seconds and quits
} catch (ConstraintViolationException e) {
Assert.fail("constraint violations: " + e.getConstraintViolations());
}
}
}
Since we don't need any properties for this application the only thing changed in this file is uncommenting the line:
conf.addResource(this.getClass().getResourceAsStream("/META-INF/properties.xml"));
If you execute the ApplicationTest.java and send messages to RabbitMQ using the Producer program as described in 2., the Test should output all the messages.
You might need to increase the time of the test to see all messages (It is set to 10sec currently).

Handling multiple certificates in Netty's SSL Handler used in Play Framework 1.2.7

I have a Java Key Store where I store certificates for each of my customer's sub-domain. I am planning to use the server alias to differentiate between multiple customers in the key store as suggested here. Play framework 1.2.7 uses Netty's SslHandler to support SSL on the server-side. I tried implementing a custom SslHttpServerContextFactory that uses this solution.
import play.Play;
import javax.net.ssl.*;
import java.io.FileInputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.security.KeyStore;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Properties;
public class CustomSslHttpServerContextFactory {
private static final String PROTOCOL = "SSL";
private static final SSLContext SERVER_CONTEXT;
static {
String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
if (algorithm == null) {
algorithm = "SunX509";
}
SSLContext serverContext = null;
KeyStore ks = null;
try {
final Properties p = Play.configuration;
// Try to load it from the keystore
ks = KeyStore.getInstance(p.getProperty("keystore.algorithm", "JKS"));
// Load the file from the conf
char[] certificatePassword = p.getProperty("keystore.password", "secret").toCharArray();
ks.load(new FileInputStream(Play.getFile(p.getProperty("keystore.file", "conf/certificate.jks"))),
certificatePassword);
// Set up key manager factory to use our key store
KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
kmf.init(ks, certificatePassword);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
tmf.init(ks);
final X509KeyManager origKm = (X509KeyManager) kmf.getKeyManagers()[0];
X509KeyManager km = new X509KeyManagerWrapper(origKm);
// Initialize the SSLContext to work with our key managers.
serverContext = SSLContext.getInstance(PROTOCOL);
serverContext.init(new KeyManager[]{km}, tmf.getTrustManagers(), null);
} catch (Exception e) {
throw new Error("Failed to initialize the server-side SSLContext", e);
}
SERVER_CONTEXT = serverContext;
}
public static SSLContext getServerContext() {
return SERVER_CONTEXT;
}
public static class X509KeyManagerWrapper implements X509KeyManager {
final X509KeyManager origKm;
public X509KeyManagerWrapper(X509KeyManager origKm) {
this.origKm = origKm;
}
public String chooseServerAlias(String keyType,
Principal[] issuers, Socket socket) {
InetAddress remoteAddress = socket.getInetAddress();
//TODO: Implement alias selection based on remoteAddress
return origKm.chooseServerAlias(keyType, issuers, socket);
}
#Override
public String chooseClientAlias(String[] keyType,
Principal[] issuers, Socket socket) {
return origKm.chooseClientAlias(keyType, issuers, socket);
}
#Override
public String[] getClientAliases(String s, Principal[] principals) {
return origKm.getClientAliases(s, principals);
}
#Override
public String[] getServerAliases(String s, Principal[] principals) {
return origKm.getServerAliases(s, principals);
}
#Override
public X509Certificate[] getCertificateChain(String s) {
return origKm.getCertificateChain(s);
}
#Override
public PrivateKey getPrivateKey(String s) {
return origKm.getPrivateKey(s);
}
}
}
But, this approach did not work for some reason. I get this message in my SSL debug log.
X509KeyManager passed to SSLContext.init(): need an X509ExtendedKeyManager for SSLEngine use
This is the SSL trace, which fails with "no cipher suites in common". Now, I switched the wrapper to:
public static class X509KeyManagerWrapper extends X509ExtendedKeyManager
With this change, I got rid of the warning, but I still see the same error as before "no cipher suites in common" and here is the SSL trace. I am not sure why the delegation of key manager won't work.
Some more information that may be useful in this context.
Netty uses javax.net.ssl.SSLEngine to support SSL in NIO server.
As per the recommendation in this bug report, it is intentional that X509ExtendedKeyManager must be used with an SSLEngine. So, the wrapper must extend X509ExtendedKeyManager.
This is hindering me to move further with the custom alias selection logic in X509KeyManagerWrapper. Any clues on what might be happening here? Is there any other way to implement this in Netty/Play? Appreciate any suggestions.
SSLEngine uses the chooseEngineServerAlias method to pick the certificate to use (in server mode) - not the chooseServerAlias method.
The default chooseEngineServerAlias implementation actually returns null, which is what causes the "no cipher suites in common" message - you need a certificate to know which cipher suites can be used (e.g. ECDSA can only be used for authentication if the certificate has an ECC public key, etc.) There are actually some cipher suites which can be used without a certificate, however, these are typically disabled as they are vulnerable to MITM attacks.
Therefore, you should also override chooseEngineServerAlias, and implement your logic to select the certificate based on the IP address there. As Netty only uses SSLEngine, what chooseServerAlias does doesn't matter - it'll never be called.
Java 8 also has support for server-side SNI, which allows you to use several certificates across many hostnames with a single IP address. Most web browsers support SNI - the notable exceptions are IE running on Windows XP and some old versions of Android, however, usage of these is declining. I have created a small example application demonstrating how to use SNI in Netty on GitHub. The core part of how it works is by overriding chooseEngineServerAlias - which should give you enough hints, even if you want to use the one certificate per IP address technique instead of SNI.
(I posted a similar answer to this on the Netty mailing list, where you also asked this question - however, my post seems to have not yet been approved, so I thought I'd answer here too so you can get an answer sooner.)

Can I get HttpClient to use Weblogic's custom keystore / truststore settings?

My application is using Apache's HttpClient 3.1 deployed on Weblogic 10.3 to perform a POST using SSL mutual authentication. I can get this to work using the following system properties to configure the keystore & truststore:-
-Djavax.net.ssl.keyStore=C:\Keystore\KEYSTORE.jks
-Djavax.net.ssl.keyStorePassword=changeit
-Djavax.net.ssl.trustStore=C:\Truststore\TRUSTSTORE.jks
-Djavax.net.ssl.trustStorePassword=changeit
Is there any way to get HttpClient to recognize and use the Weblogic custom keystore & truststore settings (as configured in the console / config.xml). Amongst other things this would provide the ability to keep the passwords "hidden" and not visible as plain text in config files / console etc.
Can anyone enlighten me?
I have been able to get HttpClient to use the custom weblogic trust store certificates for SSL connection by implementing custom TrustStrategy:
import sun.security.provider.certpath.X509CertPath;
import weblogic.security.pk.CertPathValidatorParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertPath;
import java.security.cert.CertPathParameters;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
public class WeblogicSSLTrustStrategy implements TrustStrategy {
#Override
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
validator = CertPathValidator.getInstance("WLSCertPathValidator");
CertPath certPath = new X509CertPath(Arrays.asList(chain));
// supply here the weblogic realm name, configured in weblogic console
// "myrealm" is the default one
CertPathParameters params = new CertPathValidatorParameters("myrealm", null, null);
try {
validator.validate(certPath, params);
} catch (CertPathValidatorException e) {
throw new CertificateException(e);
} catch (InvalidAlgorithmParameterException e) {
throw new CertificateException(e);
}
return true;
}
}
This code is based on Weblogic documentation. The strategy can be passed to HttpClient via SSLSocketFactory:
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
SSLSocketFactory sslSocketFactory = new SSLSocketFactory(new WeblogicSSLTrustStrategy());
schemeRegistry.register(new Scheme("https", 443, sslSocketFactory));
PoolingClientConnectionManager connectionManager = new PoolingClientConnectionManager(schemeRegistry);
DefaultHttpClient httpClient = new DefaultHttpClient(connectionManager);
The only unknown parameter is the Weblogic Realm name, which can be taken from Weblogic JMX API, or simply preconfigured. This way it does not require to instantiate the trust store or to reconfigure Weblogic startup parameters.
You might be able to obtain these values via JMX using the KeyStoreMBean. Be forewarned though, this might not be a trivial exercise due to the following:
This would require storing the keystore passwords in cleartext in your JMX client (now that you would be writing one in your application). This is insecure, and a security audit might fail due to this, depending on what the audit is meant to look for.
The MBeans might not be accessible at runtime, due to the JMX service configuration, or would have to be accessed differently in different scenarios. Assuming WebLogic 11g, the values might be made read-only, by setting the value of the EditMBeanServerEnabled attribute of the JMXMBean to false.