I am trying to connect to a 3 node Cassandra cluster from a Java client program, with Cassandra cluster configured with Client to node encryption enabled. I deployed three self signed certificate in all three nodes, and imported the public certificates to each of other nodes as per the document Client-to-node encryption. When I run the client program I am getting the following exception.
Exception in thread "main" com.datastax.driver.core.exceptions.NoHostAvailableException: All host(s) tried for query failed (tried: clm-pun-swpry4/10.133.181.157:9042 (com.datastax.driver.core.exceptions.TransportException: [clm-pun-swpry4/10.133.181.157:9042] Channel has been closed), clm-pun-swpryf/10.133.181.156:9042 (com.datastax.driver.core.exceptions.TransportException: [clm-pun-swpryf/10.133.181.156:9042] Channel has been closed), clm-pun-sqbgda/10.133.172.70:9042 (com.datastax.driver.core.exceptions.TransportException: [clm-pun-sqbgda/10.133.172.70:9042] Channel has been closed))
at com.datastax.driver.core.ControlConnection.reconnectInternal(ControlConnection.java:233)
at com.datastax.driver.core.ControlConnection.connect(ControlConnection.java:79)
at com.datastax.driver.core.Cluster$Manager.init(Cluster.java:1483)
at com.datastax.driver.core.Cluster.init(Cluster.java:159)
at com.datastax.driver.core.SessionManager.initAsync(SessionManager.java:78)
at com.datastax.driver.core.SessionManager.executeAsync(SessionManager.java:139)
at com.datastax.driver.core.AbstractSession.execute(AbstractSession.java:68)
at com.datastax.driver.core.AbstractSession.execute(AbstractSession.java:43)
at clm.bmc.saas.incubator.ClientToNodeExample.main(ClientToNodeExample.java:28)
My Cassandra configuration in cassandra.yaml (individual self signed certificate for each of the 3 nodes):
client_encryption_options:
enabled: true
# If enabled and optional is set to true encrypted and unencrypted connections are handled.
optional: false
keystore: /opt/secure/keystore.clm-pun-swpry4
keystore_password: changeit
# require_client_auth: false
# Set trustore and truststore_password if require_client_auth is true
# truststore: conf/.truststore
# truststore_password: cassandra
# More advanced defaults below:
protocol: TLSv1.2
algorithm: SunX509
store_type: JKS
cipher_suites: [TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA]
My Java program:
public class ClientToNodeExample {
private static final Logger LOGGER = LoggerFactory.getLogger(ClientToNodeExample.class);
public static void main(String[] args) {
ClientToNodeExample example = new ClientToNodeExample();
Session session = example.getCluster("C:\\install\\ssl\\cassandraCluster.ks", "changeit",
new String[]{"clm-pun-sqbgda", "clm-pun-swpryf", "clm-pun-swpry4"}, 9042).newSession();
ResultSet results = session.execute("SELECT * FROM entity_space.mo;");
LOGGER.info("NumberOfRows:" + results.all().size());
session.close();
}
private Cluster getCluster(String trustStoreLocation, String trustStorePassword, String[] host, int port) {
Cluster cluster;
SSLContext sslcontext = null;
try {
InputStream is = ClientToNodeExample.class.getResourceAsStream(trustStoreLocation);
KeyStore keystore = KeyStore.getInstance("jks");
char[] pwd = trustStorePassword.toCharArray();
keystore.load(is, pwd);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keystore);
TrustManager[] tm = tmf.getTrustManagers();
sslcontext = SSLContext.getInstance("TLS");
sslcontext.init(null, tm, null);
} catch (Exception e) {
LOGGER.error("ERROR", e);
}
JdkSSLOptions sslOptions = JdkSSLOptions.builder().withSSLContext(sslcontext).build();
cluster = Cluster.builder().addContactPoints(host).withPort(port).withSSL(sslOptions).build();
return cluster;
}
}
However if I try to check the certificate from each node with the following keytool command, I get the certificate:
keytool -printcert -sslserver clm-pun-sqbgda:9042 -rfc
Can anyone please help where I am going wrong?
Cassandra version:3.11.0
cassandra-driver-core : 3.1.4
First I would try to connect via cqlsh --ssl (you need to do some extra steps):
convert server certificate to PKCS12 format
convert PKCS12 to PEM
modify cqlshrc file in order to use the PEM certificate.
If this is ok, then the setup is correct and I would remove the sslOptions and provide
-Djavax.net.ssl.trustStore
-Djavax.net.ssl.trustStorePassword
-Djavax.net.debug=ssl.
If this is works also, then the issue is in your getCluster method.
My guess is that your keystore (cassandraCluster.ks) is not loaded correctly.
I successfully executed your code as maven project, with the keystore in the resources folder and loaded with
cluster = getCluster("/client-truststore.jks", "changeit", new String[]{"127.0.0.1"}, 9042);
Related
I have a site certificate ('tls_connection_cert') for TSL connection and a private certificate ('private_cert.p12') for authentication. Whenever I want to make connection in dart I get an error SSLV3_ALERT_HANDSHAKE_FAILURE(tls_record.cc:586) with no extra information.
Dart code:
SecurityContext context = new SecurityContext(withTrustedRoots: true)
..setTrustedCertificates('tls_connection_cert')
..usePrivateKey('private_cert.p12', password: 'password');
await SecureSocket.connect(
"service_host", 9002, context: context, onBadCertificate: _onSelfSignedCertificate).then((socket) {
print('Connected to: '
'${socket.remoteAddress.address}:${socket.remotePort}');
socket.destroy();
});
I already uses his service with a JAVA code which work perfectly, with no errors. The service in question is already used by the java code for 3 years now.
Java code:
KeyManager[] privateCert; // private_cert.p12
TrustManager[] publicCert; // tls_connection_cert
SSLContext ssl = SSLContext.getInstance("TLS");
ssl.init(privateCert.getKeyManagers(), publicCert.getTrustManagers(), null);
SSLSocket socket = (SSLSocket)ssl.getSocketFactory().createSocket("service_host", 9002);
socket.startHandshake();
socket.close();
Online service only allow TLSv1.2. Project is a Flutter project build in Android Studio on Windows 10 machine.
How can I found what’s wrong with dart code?
I'm trying to consume an IBM MQ (version 8.0.0.8) with SSL using a Java client built basically with:
Oracle JKD 8 and IBM JRE 7 (for testing purposes I have one client for each of them)
com.ibm.mq.allclient-9.1.0.0.jar
javax.jms-api-2.0.1.jarspring-jms-4.3.7.RELEASE.jar
spring-jms-4.3.7.RELEASE.jar
The MQ is a Request/Reply type.
I have the correct certificate and all MQ properties set, but for some reason the connection “drops” and I get no errors on my client side and my requests never get any response and keeps running “forever”, never getting any response. The only clue that I have is an error message in the MQ log that says:
Process(31600.16) User(QMQM) Jobname(JOB_NAME)
Host(HOST_NAME)
VRMF(8.0.0.8) QMgr(MANAGER_NAME)
AMQ9638: SSL communications error for channel 'CHANNEL_NAME'. EXPLANATION:
Cause . . . . . : An unexpected SSL communications error occurred for a channel, as reported in the preceding messages. The
channel is 'CHANNEL_NAME';
The strange thing is that the SSL Handshake happens, my certificate is accepted by the MQ, but for some reason something happens after this. I’m trying using both Oracle JRE 8 and IBM JRE 7. Maybe is something on MQ side (IBM MQ v8.0.0.8) or some configuration that I’m missing on my side.
I have already installed the JCE Unlimited Policies, so the problem is not CipherSpec X CipherSuite.
I'm using -Djavax.net.debug=all and I can see that my certificate is being used correctly and I can't see any problems there...
My contact point on the MQ Team told me that for some reason my application is revoking the certificate (something related with CLR), but I have no idea why this would happen.
My Java code:
public Message callMQ() {
Message message = null;
try {
MQConnectionFactory factory = mqQueueConnectionFactory();
JMSContext context = factory.createContext();
Destination requestQueue = context.createQueue("queue:///REQUEST_QUEUE");
Destination replyQueue = context.createQueue("queue:///REPLY_QUEUE");
JmsTemplate jmsTemplate = new JmsTemplate(factory);
FIXMLRootInbound inbound = new FIXMLRootInbound();
String xml = XmlUtil.xmlObjectToString(inbound);
message = jmsTemplate.sendAndReceive(requestQueue,
session -> {
Message req = session.createTextMessage(xml);
req.setJMSCorrelationID(UUID.randomUUID().toString());
req.setJMSDestination(requestQueue);
req.setJMSReplyTo(replyQueue);
return req;
});
} catch (Throwable e) {
e.printStackTrace();
}
return message;
}
private MQConnectionFactory mqQueueConnectionFactory() throws NoSuchAlgorithmException, KeyStoreException,
IOException, CertificateException, UnrecoverableKeyException, KeyManagementException, JmsException {
SSLSocketFactory sslSocketFactory = sslContext().getSocketFactory();
MQEnvironment.sslSocketFactory = sslSocketFactory;
MQEnvironment.sslCipherSuite = "TLS_RSA_WITH_AES_256_CBC_SHA";
MQEnvironment.sslFipsRequired = false;
MQConnectionFactory mqQueueConnectionFactory = new MQQueueConnectionFactory();
mqQueueConnectionFactory.setHostName(host);
try {
mqQueueConnectionFactory.setTransportType(JMSC.MQJMS_TP_CLIENT_MQ_TCPIP);
mqQueueConnectionFactory.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE,
WMQConstants.WMQ_CM_CLIENT);
mqQueueConnectionFactory.setQueueManager(queueManager);
mqQueueConnectionFactory.setSSLCipherSuite("TLS_RSA_WITH_AES_256_CBC_SHA");
mqQueueConnectionFactory.setCCSID(285);
mqQueueConnectionFactory.setChannel(channel);
mqQueueConnectionFactory.setPort(port);
mqQueueConnectionFactory.setSSLSocketFactory(sslSocketFactory);
mqQueueConnectionFactory.setSSLFipsRequired(false);
} catch (Exception e) {
log.error("Error creating MQQueueConnectionFactory.", e);
}
return mqQueueConnectionFactory;
}
private SSLContext sslContext() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
try (InputStream cert = new FileInputStream("C:\\myplace\\Dev\\Certificates\\MY_KEYSTORE.jks")) {
final KeyStore caCertsKeyStore = KeyStore.getInstance("JKS");
caCertsKeyStore.load(cert, "changeit".toCharArray());
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX");
PKIXRevocationChecker rc = (PKIXRevocationChecker)cpb.getRevocationChecker();
rc.setOptions(EnumSet.of(
PKIXRevocationChecker.Option.PREFER_CRLS,
PKIXRevocationChecker.Option.ONLY_END_ENTITY,
PKIXRevocationChecker.Option.SOFT_FAIL,
PKIXRevocationChecker.Option.NO_FALLBACK));
PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(caCertsKeyStore, new X509CertSelector());
pkixParams.addCertPathChecker(rc);
kmf.init(caCertsKeyStore, "changeit".toCharArray());
tmf.init( new CertPathTrustManagerParameters(pkixParams) );
final SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
return sslContext;
} catch (Exception e) {
throw new RuntimeException("Exception creating SSLContext", e);
}
}
Since you are using the 9.1.0.0 com.ibm.mq.allclient.jar you do not need all of the code you have related to the key store, for example:
SSLSocketFactory sslSocketFactory = sslContext().getSocketFactory();
//Note that MQEnvironment is used with IBM MQ Classes for Java not IBM MQ Classes for JMS
MQEnvironment.sslSocketFactory = sslSocketFactory;
MQEnvironment.sslCipherSuite = "TLS_RSA_WITH_AES_256_CBC_SHA";
MQEnvironment.sslFipsRequired = false;
mqQueueConnectionFactory.setSSLSocketFactory(sslSocketFactory);
private SSLContext sslContext() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
try (InputStream cert = new FileInputStream("C:\\myplace\\Dev\\Certificates\\MY_KEYSTORE.jks")) {
final KeyStore caCertsKeyStore = KeyStore.getInstance("JKS");
caCertsKeyStore.load(cert, "changeit".toCharArray());
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX");
PKIXRevocationChecker rc = (PKIXRevocationChecker)cpb.getRevocationChecker();
rc.setOptions(EnumSet.of(
PKIXRevocationChecker.Option.PREFER_CRLS,
PKIXRevocationChecker.Option.ONLY_END_ENTITY,
PKIXRevocationChecker.Option.SOFT_FAIL,
PKIXRevocationChecker.Option.NO_FALLBACK));
PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(caCertsKeyStore, new X509CertSelector());
pkixParams.addCertPathChecker(rc);
kmf.init(caCertsKeyStore, "changeit".toCharArray());
tmf.init( new CertPathTrustManagerParameters(pkixParams) );
final SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
return sslContext;
} catch (Exception e) {
throw new RuntimeException("Exception creating SSLContext", e);
}
}
You can instead replace that with setting the following two system properties, this will work with both Oracle and IBM Java:
System.setProperty("javax.net.ssl.keyStore", "C:\\myplace\\Dev\\Certificates\\MY_KEYSTORE.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "changeit");
The above settings have always worked for IBM Java, but with Oracle Java this did not work with older versions of MQ. It was fixed for Oracle java in the following versions of IBM MQ (Base 9.0 and 9.1 have the same fix):
Version Maintenance Level
v7.1 7.1.0.8
v7.5 7.5.0.7
v8.0 8.0.0.5
IBM Java and Oracle Java have different CipherSuite names, these are documented in the IBM MQ v9.1 Knowledge center page "TLS CipherSpecs and CipherSuites in IBM MQ classes for JMS.
You have specified TLS_RSA_WITH_AES_256_CBC_SHA in the posted code, this would be the SSLCIPH value on the MQ Queue manager SVRCONN channel, and would map to the following CipherSuites:
IBM Java: SSL_RSA_WITH_AES_256_CBC_SHA
Oracle Java: TLS_RSA_WITH_AES_256_CBC_SHA
Related to the above, if you are using Oracle Java you need to set the following system property so that the MQ JMS classes will be able to use the right mapping of the Oracle CipherSuite name:
System.setProperty("com.ibm.mq.cfg.useIBMCipherMappings", "false");
The error you provided appears to be from the IBM i MQ Queue manager, but does not provide enough information to diagnose the problem.
The error states the following:
An unexpected SSL communications error occurred for a channel, as reported in the preceding messages.
Can you edit your question and provide the details from the "preceding messages".
You stated
for some reason my application is revoking the certificate (something related with CLR)
It could be that the MQ queue manager is attempting itself to connect to the OCSP server specified in the your client cert's AuthorityInfoAccess (AIA) certificate extension. If MQ can not reach this OCSP server with a default configuration the connection will be denied. If you are unable to update your network to allow connection to the OCSP server then you can disable this check but note that you will not know if a cert is revoked. To disable the checking the following can be added to the queue manager's qm.ini file SSL stanza:
SSL:
OCSPAuthentication=Optional
OCSPCheckExtensions=no
One last comment, the CipherSuite you listed in your example code TLS_RSA_WITH_AES_256_CBC_SHA is a TLS1.0 CipherSuite. Like SSL before it this and TLS1.1 are commonly deprecated across many industries. I looked for reference to post and a google of "tls 1.0 end of life" gives many references.
Quoting one "TLS 1.0 end-of-life on June 30th, 2018" below:
The Deadline The PCI Council is responsible for determining when older protocols are phased out. They initially decided TLS 1.0 would
reach end of life on 30 June 2016, later extending the date to 30 June
2018. The final deadline has past and all web servers, web browsers, web software and email applications have had to discontinue TLS 1.0
support, or fall foul of important security updates.
I would suggest choosing one listed as TLS1.2 in the Knowledge center page I linked to above for example TLS_RSA_WITH_AES_256_CBC_SHA256.
Can anybody tell how we interact with security enables Riak KV Cluster using java client.
I tried with following java code to interact with security enabled riak cluster, but getting SSLEngine Problem, Below is the java code ......
InputStream inputStream = null;
KeyStore ks = null;
try {
inputStream = new FileInputStream("/etc/ssl/certs/rootCA.pem");
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
X509Certificate caCert = (X509Certificate) certFactory.generateCertificate(inputStream);
inputStream.close();
ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, "password".toCharArray());
ks.setCertificateEntry("cacert", caCert);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (RiakConfig.class) {
List<RiakNode> riakNodeList = new ArrayList<RiakNode>();
for (final String riakServer : riakServerArray) {
RiakNode node = new RiakNode.Builder()
.withMinConnections(10)
.withMinConnections(50)
.withRemoteAddress(riakServer.split(":")[0])
.withRemotePort(Integer.parseInt(riakServer.split(":")[1]))
.withAuth("riakuser", "riakuser", ks)
.build();
riakNodeList.add(node);
}
cluster = new RiakCluster.Builder(riakNodeList).build();
cluster.start();
}
suggenst anyone how we do that???
getting SSLEngine Problem
Please, always provide the specific problem you are getting. "getting SSLEngine Problem" is a useless bit of information.
Based on this document it appears you are using the correct steps. Without more information about the security issue you are getting, further help is impossible. Most likely you do not have your certificates set up correctly. The RabbitMQ documentation includes a comprehensive TLS/SSL troubleshooting guide that can help you determine if your certificates were created correctly.
Additionally, I suggest that you review how the Riak Java Client sets up certificates and then uses them.
This part of the Makefile is where certs are imported with keytool:
https://github.com/basho/riak-java-client/blob/develop/Makefile#L43-L62
This is a class that uses the key store to create connections for use in tests:
https://github.com/basho/riak-java-client/blob/develop/src/test/java/com/basho/riak/client/core/operations/itest/RiakJKSConnection.java
Achually this is working fine for single node, in cluster i am facing SSL Engine Problem.
I have an Azure Service bus topic set up called "state-changed" and it has a subscription called "reverb". I'm trying to set up a method with #JmsListener to subscribe to the topic but am getting an error:
2017-03-22 18:34:41.049 WARN 23356 --- [enerContainer-6] o.s.j.l.DefaultMessageListenerContainer : Setup of JMS message listener invoker failed for destination 'state-changed' - trying to recover. Cause: The messaging entity 'sb://[MySERVICEBUS].servicebus.windows.net/state-changed' could not be found. TrackingId:d2b442f79e0f44bdb449861ea57155ce_G44, SystemTracker:gateway6, Timestamp:3/22/2017 6:34:37 PM
javax.jms.JMSException: The messaging entity 'sb://[MySERVICEBUS].servicebus.windows.net/state-changed' could not be found. TrackingId:d2b442f79e0f44bdb449861ea57155ce_G44, SystemTracker:gateway6, Timestamp:3/22/2017 6:34:37 PM
at org.apache.qpid.amqp_1_0.jms.impl.TopicSubscriberImpl.createClientReceiver(TopicSubscriberImpl.java:111) ~[qpid-amqp-1-0-client-jms-0.32.jar:0.32]
at org.apache.qpid.amqp_1_0.jms.impl.MessageConsumerImpl.<init>(MessageConsumerImpl.java:129) ~[qpid-amqp-1-0-client-jms-0.32.jar:0.32]
at org.apache.qpid.amqp_1_0.jms.impl.TopicSubscriberImpl.<init>(TopicSubscriberImpl.java:46) ~[qpid-amqp-1-0-client-jms-0.32.jar:0.32]
at org.apache.qpid.amqp_1_0.jms.impl.SessionImpl.createDurableSubscriber(SessionImpl.java:544) ~[qpid-amqp-1-0-client-jms-0.32.jar:0.32]
at org.apache.qpid.amqp_1_0.jms.impl.SessionImpl.createDurableSubscriber(SessionImpl.java:59) ~[qpid-amqp-1-0-client-jms-0.32.jar:0.32]
at org.springframework.jms.listener.AbstractMessageListenerContainer.createConsumer(AbstractMessageListenerContainer.java:870) ~[spring-jms-4.3.6.RELEASE.jar:4.3.6.RELEASE]
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.createListenerConsumer(AbstractPollingMessageListenerContainer.java:215) ~[spring-jms-4.3.6.RELEASE.jar:4.3.6.RELEASE]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.initResourcesIfNecessary(DefaultMessageListenerContainer.java:1189) ~[spring-jms-4.3.6.RELEASE.jar:4.3.6.RELEASE]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.invokeListener(DefaultMessageListenerContainer.java:1165) ~[spring-jms-4.3.6.RELEASE.jar:4.3.6.RELEASE]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.executeOngoingLoop(DefaultMessageListenerContainer.java:1158) ~[spring-jms-4.3.6.RELEASE.jar:4.3.6.RELEASE]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.run(DefaultMessageListenerContainer.java:1055) ~[spring-jms-4.3.6.RELEASE.jar:4.3.6.RELEASE]
at java.lang.Thread.run(Unknown Source) [na:1.8.0_77]
I have been using this blog post to try and get everything up and running: http://ramblingstechnical.blogspot.co.uk/p/using-azure-service-bus-with-spring-jms.html
I can add messages to the topic with JmsTemplate and read messages from it using the plain old Java JMS libraries outlined in the Azure docs: https://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-java-how-to-use-jms-api-amqp so I know my topic does work and is accessable, it just seems to be when I configure it with Spring that I'm doing something wrong.
My configuration looks like:
#Configuration
public class JmsConfiguration
{
#Bean
public JmsListenerContainerFactory topicJmsListenerContainerFactory() throws NamingException
{
DefaultJmsListenerContainerFactory returnValue = new DefaultJmsListenerContainerFactory();
Context context = context();
ConnectionFactory cf = connectionFactory(context);
returnValue.setConnectionFactory(cf);
returnValue.setSubscriptionDurable(Boolean.TRUE);
return returnValue;
}
private Context context() throws NamingException
{
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.qpid.amqp_1_0.jms.jndi.PropertiesFileInitialContextFactory");
env.put(Context.PROVIDER_URL, "src/main/resources/servicebus.properties");
Context context = new InitialContext(env);
return context;
}
/**
* #param context
* #return
* #throws NamingException
*/
private ConnectionFactory connectionFactory(Context context) throws NamingException
{
ConnectionFactory cf = (ConnectionFactory) context.lookup("SBCF");
return cf;
}
}
servicebus.properties (with username and password etc redacted):
# servicebus.properties - sample JNDI configuration
# Register a ConnectionFactory in JNDI using the form:
# connectionfactory.[jndi_name] = [ConnectionURL]
connectionfactory.SBCF=amqps://[USER]:[PASSWORD]#[MYSERVICEBUS]
# Register some queues in JNDI using the form
# queue.[jndi_name] = [physical_name]
# topic.[jndi_name] = [physical_name]
queue.workflow = workflow
topic.state-changed = stage-changed
And finally my listener class:
#Component
public class TestListener
{
Logger logger = LoggerFactory.getLogger(LoggingWorkflowEventHandler.class);
#JmsListener(destination = "state-changed", containerFactory = "topicJmsListenerContainerFactory", subscription = "reverb")
public void onMessage(String message)
{
logger.info("Received message from topic: {}", message);
}
}
If anyone has ever managed to get this working I'd be grateful for some pointers.
Your error message indicates the name of your destination is not found (message entity not found).
Note that you need to tell Azure your Subscription name in a specific way like this:
<TopicName>/Subscriptions/<SubscriptionName>
In your case:
state-changed/Subscriptions/reverb
Hope that helps
Cheers
Seb
If you use Spring Boot you can use prepared Azure ServiceBus JMS Spring Boot Starter where it works out of box.
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-servicebus-jms-spring-boot-starter</artifactId>
<version>2.3.5</version>
</dependency>
See a tutorial page https://learn.microsoft.com/en-us/azure/developer/java/spring-framework/configure-spring-boot-starter-java-app-with-azure-service-bus
Create or change a Trustrore:
As we want to establish a secured amqps connection to the Service Bus, we need to have all required SSL certificates stored in a truststore. As it seems that none of the existing ones contains the required certificates I - for transparency - created a new one like this:
Acquire the needed certificates by visiting https://<URL-Of-Your-Servicebus>, e.g. https://XXXXX.servicebus.cloudapi.de in a browser. Then click on the "lock" in the URL (or whatever your browser shows for a secure connection) and open the certificate from there.
Save the current certificate:
[]
When asked for the format to export, choose "DER binary" and save it as a ".cer" file, e.g. "1.cer"
Most likely you can see that your certificate is based on a certificate chain, which means it depends on other certificates. For each of those click "show certificate":
[]
And save that one in the same way as the one before. Repeat until you reach the root-certificate. In this example you will end up with three *.cer files. For further reference I will call them 1.cer, 2.cer and 3.cer
You should now create a new Truststore file for those certificates
/opt/webMethods9/jvm/jvm/bin/keytool -import -file
/opt/webMethods9/IntegrationServer/instances/default/packages/DEV_fse/resources/1.cer -keystore azureTruststore.jks -alias "D-TRUST Root Class 3 CA 2 2009"
/opt/webMethods9/jvm/jvm/bin/keytool -import -file
/opt/webMethods9/IntegrationServer/instances/default/packages/DEV_fse/resources/2.cer -keystore azureTruststore.jks -alias "D-TRUST SSL Class 3 CA 1 2009"
/opt/webMethods9/jvm/jvm/bin/keytool -import -file
/opt/webMethods9/IntegrationServer/instances/default/packages/DEV_fse/resources/3.cer -keystore azureTruststore.jks -alias "servicebus.cloudapi.de"
You will be asked to set the password for this newly created truststore the first time. Now move the truststore to /opt/webMethods9/IntegrationServer/config/certs/trusted (for later reference). You might add it to IS as a truststore (by using the admin-UI "Security > Keystore " and "Create Truststore Alias"), but there is no technical need to do it, as in our case the IS is not using the truststore - it's only used by QPID.
Create a properties file for JNDI You need to create a servicebus.properties file to act as the data source for a pseudo JNDI Server. You can technically place that file wherever you want, but I would recommend putting it in the "resources" folder of the "XXXXXXConnection" package. This should be the content of that file:
# servicebus.properties - sample JNDI configuration
# Register a ConnectionFactory in JNDI using the form:
# connectionfactory.[jndi_name] = [ConnectionURL]
connectionfactory.SBCF = amqps://XXXXXX.servicebus.cloudapi.de?jms.username=xxxxx&jms.password=xxxxxxx&amqp.idleTimeout=120000&jms.receiveLocalOnly=true&transport.trustStoreLocation=/opt/webMethods9/IntegrationServer/config/certs/trusted/azureTruststore.jks
# Register some queues in JNDI using the form
# queue.[jndi_name] = [physical_name]
# topic.[jndi_name] = [physical_name]
queue.QUEUE = myqueue
Some Explanations:
SBCF will be the JNDI-Lookup name for the connection factory. This name is later needed in your JMS-Connection
xxxxxx.servicebus.cloudapi.de is the URL to your Service Bus
jms.username will be provided by your friendly Azure administrator
jms.password will be provided by your friendly Azure administrator. But be aware that you will need to URL-Encode what you will get from the admin before you can use it in this URL. This can for example be done by calling the IS Service pub.string:URLEncode manually in Designer.
amqp.idleTimeout needs to be set to 120000 (or higher) because of otherwise you cannot connect to SB
jms.receiveLocalOnly needs to be set to true because of otherwise you cannot connect to SB
transport.trustStoreLocation needs to point the truststore that contains all SSL Certificates that are required to create a secured (AMQPS) connection to SB
queue.QUEUE: QUEUE is the JNDI-Lookup name you will use later in your JMS-Client to send messages or in your JMS-Trigger to receive them. You should set to a something more meaningful probably. The value of this ("myqueue" in the example) is the name of the queue on the SB and has to be provided by your Azure Admin.
[]
The only two important values are:
"Initial Context Factory": org.apache.qpid.jms.jndi.JmsInitialContextFactory
"Provider URL": Must point to your created servicebus.properties, e.g. file:/opt/webMethods9/IntegrationServer/instances/default/packages/XXXXXXConnection/resources/servicebus.properties
In Java Network Programming 4th Edition, Chapter 10 about Secure Socket, there is an example of build Secure Server. The code can be find here.
I'm trying to make more simple version of the code. Here is my code:
try {
SSLContext context = SSLContext.getInstance(algorithm);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
KeyStore ks = KeyStore.getInstance("JKS");
//char[] password = System.console().readPassword("Password: "); // open the .class with command line
char[] password = {'t', 'h', 'i', 's', 'i', 's', 't', 'i', 'a', 'n'};
ks.load(new FileInputStream("src/jnp4e.keys"), password);
kmf.init(ks, password);
context.init(kmf.getKeyManagers(), null, null); // null = accept the default
// wipe the password
Arrays.fill(password, '0');
SSLServerSocketFactory factory
= context.getServerSocketFactory();
SSLServerSocket server
= (SSLServerSocket) factory.createServerSocket(PORT);
// add anonymous (non-authenticated) cipher suites
String[] supported = server.getSupportedCipherSuites();
String[] anonCipherSuitesSupported = new String[supported.length];
int numAnonCipherSuitesSupported = 0;
for (int i = 0; i < supported.length; i++) {
if (supported[i].indexOf("_anon_") > 0) {
anonCipherSuitesSupported[numAnonCipherSuitesSupported++]
= supported[i];
}
}
String[] oldEnabled = server.getEnabledCipherSuites();
String[] newEnabled = new String[oldEnabled.length
+ numAnonCipherSuitesSupported];
System.arraycopy(oldEnabled, 0, newEnabled, 0, oldEnabled.length);
System.arraycopy(anonCipherSuitesSupported, 0, newEnabled,
oldEnabled.length, numAnonCipherSuitesSupported);
server.setEnabledCipherSuites(newEnabled);
System.out.println("OK..");
// Now all the set up is complete and we can focus
// on the actual communication.
while (true) {
// This socket will be secure,
// but there's no indication of that in the code!
try (Socket theConnection = server.accept()) {
InputStream in = theConnection.getInputStream();
Reader r = new InputStreamReader(in, "UTF-8");
int c;
while ((c = r.read()) != -1) {
System.out.write(c);
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
} catch (IOException | KeyManagementException | KeyStoreException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException ex) {
ex.printStackTrace();
}
The only diffence is in my code I create a Reader so the server can read characters.
I tried this server with simple client that send text. Here is the Client:
int port = 7000; // default https port
String host = "localhost";
SSLSocketFactory factory
= (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket socket = null;
try {
socket = (SSLSocket) factory.createSocket(host, port);
// enable all the suites
String[] supported = socket.getSupportedCipherSuites();
socket.setEnabledCipherSuites(supported);
Writer out = new OutputStreamWriter(socket.getOutputStream(), "UTF-8");
out.write("Hello");
}catch(Exception e){
}
I run the Server first and then the Cient. But when the Server accept input from Client it throws this exception:
java.net.SocketException: Connection reset
at java.net.SocketInputStream.read(SocketInputStream.java:189)
at java.net.SocketInputStream.read(SocketInputStream.java:121)
...
UPDATE CLIENT
Based on dave answer, I add 2 lines of code flush() and close()
...
out.write("Hello");
out.flush();
socket.close();
...
But another exception arrive:
javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown
OutputStreamWriter on a socket stream apparently buffers and your client didn't .flush() or .close() so your data isn't actually sent.
If your Java program (or more exactly JVM) exits without doing .close() on a socket stream (including closing a Writer which passes through to the Stream) handling depends on the platform; on Windows it sends a RST which causes the "Connection reset" exception you see at the peer. Unix closes the connection normally at the TCP level, which is actually not fully normal for SSL/TLS, but "close enough" (as it were) that Java treats it as EOF.
Edit for followon question:
Server getting SSLHandshakeException "received alert bad_certificatecertificate_unknown" which theoretically could mean a few things but almost always means that the certificate the server is using (from the keystore you loaded, along with matching privatekey) is not signed by a CA (Certificate Authority) trusted by the client.
The code you show for the client doesn't do anything to set or alter its truststore; if there isn't code elsewhere that does so, or external settings like the java commandline option -Dx=y to set system properties, the client will use the JSSE default truststore, which is the file JRE/lib/security/jssecacerts if it exists otherwise the file JRE/lib/security/cacerts (where JRE is the directory where your JRE is installed; if you use a JDK, the JRE is a subdirectory of the JDK directory). If you (and anyone else on your system) haven't modified these files since the JRE was installed, jssecacerts doesn't exist and cacerts contains a set of "well-known root" CAs determined by Oracle, like Verisign and Equifax etc.
Thus, you need to either:
use a certificate issued by a well-known CA; if you don't already have such a cert you have to obtain it from the CA by proving (at least) your control of the domain name(s) certified and depending on the CA possibly paying a fee; if you do have or get such a cert, install it in your keystore, in the privatekey entry, with any chain certs (for well-known CAs there almost always is at least one chain cert).
use a certificate issued by any other CA, including an adhoc CA you make up, and including as the limit case a selfsigned certificate which is implicitly its own CA, such as the one keytool -genkeypair generates automatically; and put the CA certificate for that CA (or that selfsigned cert) into the truststore used by the client. For that there are two ways:
put the server's CA cert (or selfsigned cert) in the default truststore file of the JRE used by the client. This does affect any other programs sharing that default truststore, which is potentially all other programs using that JRE. If you use jssecacerts it only affects JSSE, if you use cacerts it also affects the verification of signed code (if any), plus it gets wiped out if you upgrade your JRE in place, as usually is automatic on Windows.
create (or reuse) another truststore, put the server's CA cert in there, and have the client use that nondefault truststore. There are several options for that: set the system properties for the default truststore externally, set them explicitly in your program (before first use of JSSE!), explicitly load a "keystore" file (actually containing the cert(s)) and use its trustmanager in a nondefault SSLSocketFactory much like your server code does for keymanager, or even write your own trustmanager with whatever store(s?) you like and use that similarly.
Edit#2 Simple Example
Covering all these options in detail would be much too long, but one simple option is as follows.
Generate keypair and (default) selfsigned certificate:
keytool -genkeypair -keystore ksfile -keyalg RSA
For the prompt "first and last name" (which is actually the CommonName attribute in the cert) enter the name of the server, in particular the name the client(s) will use to connect to the server; in the question this is "localhost". The other name fields don't matter; fill or omit them as you like, except that Country if used must be 2 letters as the prompt says. Instead of answering the prompts, you can add on the command line -dname "CN=common_name_value". If you have more than one name for the server(s) there are some options omitted here.
For some other applications you may need to specify the entry name with -alias name; for this question it isn't needed.
Get a copy of the certificate:
keytool -exportcert -rfc -keystore ksfile [-alias name] -file certfile
In this example the client is on the same machine as the server. In another case it would be necessary to copy the contents of this file from the server to the client; this is often done most conveniently by copying the file.
Put cert in client truststore. As above there are many options, but the one you will probably see suggested the most often because it is usually the quickest to start is to just put it in the JRE default file, cacerts:
keytool -importcert -keystore JRE/lib/security/cacerts -file certfile
where JRE is the directory where your JRE (Java Runtime Environment) is installed. This depends on your OS and how you installed your JRE (or JDK, which includes a JRE) such as with a package manager or not. If you have more than one JRE and/or JDK installed, it depends which one you are using.
On Unix if you invoke java without specifying a path, which java (or in bash and perhaps other shells, type java) will tell you the full pathname that is run. Note however this is often a symbolic link to the real location, which should be in the form /somewhere/java[version]/bin/java; change the bin/java part to lib/security/cacerts.
On Windows, if you install a normal "system-wide" Java, the program you run is %windir%\system32\java.exe where windir is usually c:\windows but the actual code and files for JRE are in c:\program files\java\jreN or c:\program files (x86)\java\jreN depending on your architecture (32-bit or 64-bit) and jreN is currently jre7 or jre8 as applicable, but likely to expand in the future. Or run the Java Control Panel; in the Java tab the View button shows location(s) of the installed JRE(s).