Initialising SSLContext in case of multiple threads - java

I am trying to initialise a SSLContext with a custom truststore and keeping keystore and secureRandom as default. For the custom truststore I have a JKS file which I will use to initialise TrustManagers.
This is a server application which will get multiple concurrent requests. I am not able to understand if we should use a singleton SSLContext/SSLSocketFactory or not. Based on Are SSLContext and SSLSocketFactory createSocket thread safe?, it looks like we should create new objects for every request. But then, the below implementation will cause high latency for loading truststore and creating SSL object for every request. Is this understanding correct?
Also, it will be very helpful if I could understand how to check if a particular object can be singleton in such cases? In the documentation I generally look for mention about being thread safe, but I could not find any such information in oracle documentation for SSLContext/SSLSocketFactory.
URL url = new URL(endpoint);
connection = (HttpsURLConnection) url.openConnection();
connection.setSSLSocketFactory(getDefaultSSLSocketFactory());
public SSLSocketFactory getDefaultSSLSocketFactory() {
String path = "path to truststore";
try (FileInputStream fis = new FileInputStream(path)) {
SSLContext sslcontext = SSLContext.getInstance("TLSv1.2");
KeyStore clientKeyStore = KeyStore.getInstance("JKS");
clientKeyStore.load(fis, "password".toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(clientKeyStore);
sslcontext.init(null, trustManagerFactory.getTrustManagers(), null);
return sslcontext.getSocketFactory();
}

Related

Convert Program to use Secure Socket Connection via java-websockets library

I am attempting to convert an existing application that uses the org.java-websocket library to allow the webserver to communicate using https instead of the previous http. After researching, the only example I was able to find is here:
https://github.com/TooTallNate/Java-WebSocket/blob/master/src/main/example/SSLServerExample.java
public static void main(String[] args) throws Exception {
ChatServer chatserver = new ChatServer(
8887); // Firefox does allow multible ssl connection only via port 443 //tested on FF16
// load up the key store
String STORETYPE = "JKS";
String KEYSTORE = Paths.get("src", "test", "java", "org", "java_websocket", "keystore.jks")
.toString();
String STOREPASSWORD = "storepassword";
String KEYPASSWORD = "keypassword";
KeyStore ks = KeyStore.getInstance(STORETYPE);
File kf = new File(KEYSTORE);
ks.load(new FileInputStream(kf), STOREPASSWORD.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, KEYPASSWORD.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ks);
SSLContext sslContext = null;
sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
chatserver.setWebSocketFactory(new DefaultSSLWebSocketServerFactory(sslContext));
chatserver.start();
}
The only problem with this is that I'm hesitant because it seems to be wanting you to access the keystore.jks and provide to the store password + keypassword and also seems to expect the KeyStore file to be on the running computer (or somewhere released with the software). Isn't this a security risk?
I already have the jar signed with the keystore, perhaps there is nothing more that I need to do? Can someone point me to a different example if this is not the way to do this?
This keystore I'm using is the one provided to us by an external company to our company to sign our java applications. Perhaps this is not the keystore I should be using and need to make one for this single app independently of that one?

Use specific keystore for JMS

We have the requirement to use SSL client certificate for a JMS connection to an IBM MQ server. I already asked a question specifically for Websphere MQ but then I learned that this is mainly the job of JSSE and can be configured via Java System Properties (e.g. -Djavax.net.ssl.keyStore=<location of keyStore>).
But since there are already active keystores for other parts of the application within our WildFly 9 AS, I'm looking for a way to enable a specific keystore just for the JMS part - can this be done?
Yes it is possible for an MQ classes for JMS application to use a specific keystore and truststore when creating secure connections to a queue manager.
By default, the MQ classes for JMS will use the standard javax.net.ssl System Properties to determine which certificate store to use as the key and trust stores. However, you can customise this by building your own javax.net.ssl.SSLSocketFactory object that gets set on the JMS Connection Factory used by your application.
See the Knowledge Center for further details:
https://www.ibm.com/support/knowledgecenter/SSFKSJ_9.0.0/com.ibm.mq.dev.doc/q032450_.htm
This typically means you have to programmatically build or update a JMS Connection Factory within application code, rather than via administration only and updating a JNDI definition - which is somewhat unfortunate.
I know you have stated you are using WildFly as your application server of choice, but just for your awareness, WebSphere Application Server (WSAS) allows you to configure a JMS Connection Factory within JNDI and have a separate SSL/TLS configuration (containing certificate store information, Cipher Suites etc) that can be associated with the JMS resources. WSAS will then take care of creating the SSLSocketFactory and setting it appropriately on the JMS Connection Factory when an application uses it to create a JMS Connection or Context.
As such, you continue to define your resources (JMS and SSL) administratively via the WSAS Administration Console or wsadmin scripting without having to insert specific logic within the application to do this, which is obviously preferred.
WildFly (and other JEE app servers) might offer similar functionality, but I do not know.
This maybe a little too late, but may help others. I was able to get it to work using JmsFactoryFactory,MQConnectionFactory, JKS trustStore and keyStore generated from a Certificate with the following code:
try {
JmsFactoryFactory factoryFactory = JmsFactoryFactory.getInstance(WMQConstants.WMQ_PROVIDER);
jmsConnectionFactory = (MQConnectionFactory) factoryFactory.createConnectionFactory();
SSLContext sslContext = createSSlContext();
setSSLSystemProperties();
jmsConnectionFactory.setSSLSocketFactory(sslContext.getSocketFactory());
// Set the properties
jmsConnectionFactory.setStringProperty(WMQConstants.WMQ_HOST_NAME, hostName);
jmsConnectionFactory.setIntProperty(WMQConstants.WMQ_PORT, port);
jmsConnectionFactory.setStringProperty(WMQConstants.WMQ_CHANNEL, channel);
jmsConnectionFactory.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE, WMQConstants.WMQ_CM_CLIENT);
jmsConnectionFactory.setStringProperty(WMQConstants.WMQ_APPLICATIONNAME, APPLICATION_NAME);
jmsConnectionFactory.setBooleanProperty(WMQConstants.USER_AUTHENTICATION_MQCSP, false);
jmsConnectionFactory.setStringProperty(WMQConstants.WMQ_SSL_CIPHER_SUITE, cipherSuite);
return jmsConnectionFactory;
} catch (JMSException ex) {}
SSL Context
private SSLContext createSSlContext() throws NoSuchAlgorithmException {
SSLContext sslContext = SSLContext.getInstance("TLS");
try {
// Load KeyStore
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(toInputStream(keyStorePath), keyStorePassword.toCharArray());
// Load TrustStore
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(toInputStream(trustStorePath), trustStorePassword.toCharArray());
// Set KeyManger from keyStore
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(keyStore, keyStorePassword.toCharArray());
// Set TrustManager from trustStore
TrustManagerFactory trustFact = TrustManagerFactory.getInstance("SunX509");
trustFact.init(trustStore);
// Set Context to TLS and initialize it
sslContext.init(kmf.getKeyManagers(), trustFact.getTrustManagers(), null);
return sslContext;
} catch (Exception ex) {
LOG.error("Unable to load the SSL Config", ex);
}
return sslContext;
}
I've never worked with IBM MQ but i solved the similar task for various application containers and databases. As i can see from documentation it's possible to specify custom ssl connection factory for MQ using this method MQConnectionFactory.setSSLSocketFactory().
So yes, it's definitely possible to address your requirements and basically your task is to build a dedicated ssl socket factory for MQ connections.
Here is code snippets for this:
Utility class for generating in-memory keystores and truststores.
Java keyloader supports only pkcs8 private keys out of the box. To load pem keys some external library like BouncyCastle should be used.
It's possible to generate pkcs8 keys from pem keys using openssl.
public class KeystoreGenerator {
private KeystoreGenerator() {
}
public static KeyStore generateTrustStore(CertificateEntry certificateEntry) throws Exception {
return generateTrustStore(Collections.singletonList(certificateEntry));
}
public static KeyStore generateTrustStore(Collection<CertificateEntry> certificateEntries) throws Exception {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
for (CertificateEntry certificateEntry : certificateEntries) {
Certificate certificate = certFactory.generateCertificate(certificateEntry.getCertificate());
keyStore.setCertificateEntry(certificateEntry.getAlias(), certificate);
}
return keyStore;
}
public static KeyStore generateKeystore(PrivateKeyCertificateEntry privateKeyCertificateEntry) throws Exception {
return generateKeystore(Collections.singletonList(privateKeyCertificateEntry));
}
public static KeyStore generateKeystore(Collection<PrivateKeyCertificateEntry> privateKeyCertificateEntries) throws Exception {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
for (PrivateKeyCertificateEntry privateKeyCertificateEntry : privateKeyCertificateEntries) {
Certificate certificate = certFactory.generateCertificate(privateKeyCertificateEntry.getCertificate());
keyStore.setCertificateEntry(privateKeyCertificateEntry.getAlias(), certificate);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(IOUtils.toByteArray(privateKeyCertificateEntry.getKey()));
PrivateKey privateKey = keyFactory.generatePrivate(spec);
keyStore.setKeyEntry(privateKeyCertificateEntry.getAlias(), privateKey,
privateKeyCertificateEntry.getPassword(), new Certificate[]{certificate});
}
return keyStore;
}
public static class CertificateEntry {
private final InputStream certificate;
private final String alias;
// constructor, getters and setters
}
public static class PrivateKeyCertificateEntry {
private final InputStream key;
private final InputStream certificate;
private final String alias;
private final char[] password;
// constructor, getters and setters
}
}
The next code creates ssl socket factory for MQ using dedicated keystore and truststore. This code loads keys and certificates from disk as class path resources. It's also possible to store them only in memory using OS environment variables and some extra effort during client application deployment.
public SSLSocketFactory generateMqSSLSocketFactory() throws Exception {
KeyStore keyStore = KeystoreGenerator.generateKeystore(new KeystoreGenerator.PrivateKeyCertificateEntry(
getClass().getResourceAsStream("/keys/mq-client-key.pkcs8"),
getClass().getResourceAsStream("/keys/mq-client-certificate.pem"),
"mq_client", "changeit".toCharArray()
));
// Generate keystore to authorize client on server
KeyStore trustStore = KeystoreGenerator.generateTrustStore(new KeystoreGenerator.CertificateEntry(
getClass().getResourceAsStream("/keys/mq-server-certificate.pem"), "mq_server"));
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, "changeit".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return sslContext.getSocketFactory();
}
And then set this ssl socket factory to mq connection factory using MQConnectionFactory.setSSLSocketFactory() method. Seems that IBM MQ is a proprietary library so unfortunately i can't test it, but i guess such configuration should work.

Is storing a private key possible to be secure?

This question is about a project of mine. I have a server-side application running on my Raspberry Pi, while the client-side application is supposed to be distributed on all my other devices. The following exzerpt is showing the code of the client-side.
I've been digging the Internet and what I have gathered is that in the end there no 100% safe way to store a private key. However, it is considered good practice to safe all keys in a KeyStore. The following is my solution to it, but my problem is: the KeyStore-file, client.jks, lies inside the JAR, together with the hardcoded password. client.jks contains the server-side's public key and the client-side's private key.
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream(KEYSTORE), PASSWORD.toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(keyStore, PASSWORD.toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
SSLSocket connection = (SSLSocket) sslContext.getSocketFactory().createSocket("localhost", 6789);
System.out.println("Connection successful!");
The Strings KEYSTORE and PASSWORD contain the KeyStore-file and the password, both hardcoded.
Is this really the best practice or am I missing something? Is the new SecureRandom object created for every connection enough to make the connection safe?
Best regards and thank you for your answers!

Validation of a certificate chain when opening a SSLSocket in Java

My Setup
My goal is to set up a SSL/TLS secured connection (explicit) with an FTP-Server.
The appropriate Root CA Certificate is stored in a Truststore called truststore.jks.
After the AUTH TLScommand I'm using the following code to build up the SSLSocket.
public SSLSocket enterSecureMode(Socket s) throws Exception {
KeyStore truststore = KeyStore.getInstance("JKS");
truststore.load(Files.newInputStream(Paths.get("truststore.jks")), "mypass".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(truststore);
SSLContext sCon = SSLContext.getInstance("TLS");
sCon.init(null, tmf.getTrustManagers(), null);
SSLSocketFactory sslSocketFactory = sCon.getSocketFactory();
return (SSLSocket) sslSocketFactory.createSocket(s, "<HOSTNAME>", 21, true);
}
The code itself is running fine and I received a secured Socket-Connection, but I wonder whether this can stand attacks like e.g MITM or not. I mean would that program discover an attempt of somebody trying to 'give me a Fake-Certificate'.
Therefore I'd be very happy if some more experienced SSL-Network-Programmers could enlight me :D
This is sufficient. The attacker would have to provide a certificate signed by the root CA. However you don't need all this code: you only need
System.setProperty("javax.net.ssl.trustStore", "truststore.jks");
SSLContext sCon = SSLContext.getDefault();
SSLSocketFactory sslSocketFactory = sCon.getSocketFactory();
return (SSLSocket) sslSocketFactory.createSocket(s, "<HOSTNAME>", 21, true)
If you want to be totally paranoid, after creating the SSLSocket you can get the SSLSession and then the peer certificate chain and make sure that the zeroth entry exactly matches the exact server's certificate, but this step is mostly omitted.

connect android device via MQTT with ssl

I have created an MQTT Broker and a client in java. It works perfectly using SSL too.
With a broker server and a client both written in java using paho libs, enabling SSL is easy. We need just to swich the protocol in the url from tcp to ssl IE: ssl://.messaging.internetofthings.ibmcloud.com:8883 and setting some props in te src code:
java.util.Properties sslClientProps = new java.util.Properties();
sslClientProps.setProperty("com.ibm.ssl.protocol", "TLSv1.2");
options.setSSLProperties(sslClientProps);
on the SSLContext
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, null, null);
Than a secure and encrypted connection is created (checked sniffing the packets with WireShark).
Using a specific CA trusted certificate works well too (messaging.pem file)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream certFile = MqttHandler.class.getResourceAsStream("messaging.pem");
Certificate ca = cf.generateCertificate(certFile);
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
TrustManager trustManager = TrustManagerUtils.getDefaultTrustManager(keyStore);
SSLContext sslContext = SSLContextUtils.createSSLContext("TLSv1.2", null, trustManager);
options.setSocketFactory(sslContext.getSocketFactory());
I need to use an Android Client and a custom MQTT Java Server Broker (without using SSL, MQTT publish and subscribe works well from Android too).
The trouble seems related with the creation of the SSLSocketFactory from Android.
I did this tests:
1) Setting SSL props (as I did in the src of the java client as reported above)
2) passing the CA trusted certificate on Android client (as I did in the src of the java client as reported above)
3) generate the key store from the trusted CA in BouncyCastle format (compatible with Android) as reported here http://rijware.com/accessing-a-secure-mqtt-broker-with-android/ and pass the key store on Android client:
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream keyStoreFile = getAssets().open("raw_key_file");
//keystore trusted
KeyStore keystoreTrust = KeyStore.getInstance("BKS");//Bouncy Castle format for Android
keystoreTrust.load(keyStoreFile, "mykeystorepassword".toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keystoreTrust);
SLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
options.setSocketFactory(sslContext.getSocketFactory());
4) using local trust store (CA) and client certificate:
// use local trust store (CA)
TrustManagerFactory tmf;
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream srvIn = getAssets().open("messaging.pem");
Certificate ca = cf.generateCertificate(srvIn);
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
tmf = TrustManagerFactory.getInstance("X509");
tmf.init(keyStore);
// load client certificate
KeyStore clientKeyStore = null;
InputStream clIn = getAssets().open("raw_key_file");
clientKeyStore = KeyStore.getInstance("BKS");
clientKeyStore.load(clIn, "mykeystorepassword".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
kmf.init(clientKeyStore, "mykeystorepassword".toCharArray());
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
options.setSocketFactory(sslContext.getSocketFactory());
Unfortunally with all the tests I still getting this error: MqttException (0) - javax.net.ssl.SSLException: Connection closed by peer
MqttException (0) - javax.net.ssl.SSLException: Connection closed by peer
at org.eclipse.paho.client.mqttv3.internal.ExceptionHelper.createMqttException(ExceptionHelper.java:38)
org.eclipse.paho.client.mqttv3.internal.ClientComms$ConnectBG.run(ClientComms.java:604)
at java.lang.Thread.run(Thread.java:841)
Caused by: javax.net.ssl.SSLException: Connection closed by peer
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:405)
at org.eclipse.paho.client.mqttv3.internal.SSLNetworkModule.start(SSLNetworkModule.java:89)
at org.eclipse.paho.client.mqttv3.internal.ClientComms$ConnectBG.run(ClientComms.java:590)
Maybe I still confusing with the certificates process. How to create these from scratch and using on server and client side (creating the keystore in BouncyCastle format compatible with Android) ?
Or Maybe I did something wrong using the SSLSocketFactory classes in Android...
Thanks, any suggestion is really approciated.

Categories

Resources