I need to create a javax.net.ssl.SSLSocketFactory that establish a TLS connection but ignores the validation of the server certificate. Not for HTTP protocol. I know this is not the right thing to do, but I NEED it. I don't want or like it.
I made an implementation that works but validate the server cert (as it supposes to be) implementation looks as following:
/**
* Provide a quick method to construct a SSLSocketFactory which is a TCP socket using TLS/SSL
* #param trustStore location of the trust store
* #param keyStore location of the key store
* #param trustStorePassword password to access the trust store
* #param keyStorePassword password to access the key store
* #return the SSLSocketFactory to create secure sockets with the provided certificates infrastructure
* #exception java.lang.Exception in case of something wrong happens
* */
static public SSLSocketFactory getSocketFactory ( final String trustStore, final String keyStore, final String trustStorePassword, final String keyStorePassword) throws Exception
{
// todo check if the CA needs or can use the password
final FileInputStream trustStoreStream = new FileInputStream(trustStore);
final FileInputStream keyStoreStream = new FileInputStream(keyStore);
// CA certificate is used to authenticate server
final KeyStore caKs = KeyStore.getInstance("JKS");
caKs.load(trustStoreStream, trustStorePassword.toCharArray());
final TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
tmf.init(caKs);
trustStoreStream.close();
// client key and certificates are sent to server so it can authenticate us
final KeyStore ks = KeyStore.getInstance("JKS");
ks.load(keyStoreStream, keyStorePassword.toCharArray());
final KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX");
kmf.init(ks, keyStorePassword.toCharArray());
keyStoreStream.close();
// finally, create SSL socket factory
final SSLContext context = SSLContext.getInstance("TLSv1.2");
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return context.getSocketFactory();
}
The solution would be to create a dummy X509TrustManager class that basically accepts all server certs without checking.
Then create an instance and pass it as the 2nd argument in your SSLContext::init call.
But seriously, this is a bad idea. If you are not going to check the CERT, then using SSL is pointless or worse1.
1 - Here's my theory. You have been told by your security folks that using MTTQ over an insecure channel is dangerous. Or they have blocked port 1883. But you have a MTTQ / SSL server that you can't be bothered to get a proper CERT for. So you are just trying to throw something together that "works". Guess what. If the people who told you to use SSL find out, they will have your guts for garters! And if you are not doing this for this reason, then ... well ... what you are doing makes no sense. Even a self-signed server CERT is better than this. You just need to add it as a trusted cert in the client-side keystore.
Related
I have stunnel running on my server with the following configuration:
[myservice]
accept = 12345
connect = 9999
verifyPeer = yes
cert = /etc/stunnel/stunnel.pem
CAfile = /etc/stunnel/androidApp.crt
Both cert and CAfile has been issued by the same private CA.
I want to achieve a secure communication between stunnel (on port 12345) and my Android application. Moreover, I want stunnel to verify the peer (that its certificate has been issued by the same CA as the stunnel's one) and on the other hand, the Android application should also verify the identity of the stunnel (server) part.
In my application I have the following code
// ...
InputStream caInputStream = ctx.getResources().openRawResource(R.raw.android_app); //PKCS12
KeyStore keyStore;
KeyManagerFactory keyManagerFactory;
SSLContext sslContext;
SSLSocketFactory sslSocketFactory;
Socket socket;
keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(caInputStream, "password".toCharArray());
keyManagerFactory = KeyManagerFactory.getInstance("X509");
keyManagerFactory.init(keyStore, "password".toCharArray());
sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), null, SecureRandom.getInstance("SHA1PRNG"));
sslSocketFactory = sslContext.getSocketFactory();
socket = sslSocketFactory.createSocket("hostname", 12345);
// ...
When the socket is created, I get the following logs from stunnel:
2021.05.13 17:01:21 LOG5[2]: Service [myservice] accepted connection from XXX.XXX.XXX.XXX:YYYYY
2021.05.13 17:01:21 LOG6[2]: Peer certificate required
2021.05.13 17:01:25 LOG3[2]: SSL_accept: 1417C0C7: error:1417C0C7:SSL routines:tls_process_client_certificate:peer did not return a certificate
2021.05.13 17:01:25 LOG5[2]: Connection reset: 0 byte(s) sent to TLS, 0 byte(s) sent to socket
At this stage I am fully aware that I am doing something fundamentally wrong (like I do not send the peer certificate), but I am a bit confused how to do that. Could you please give me a hand with this?
Cheers
This is an assumption, but it looks like the PKCS12 file you are opening does not contain a private key.
Add private key -> create CSR -> sign with CA -> import chain to key store.
Everything else looks in order.
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.
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.
Oh wise and noble Oracle,
I'm adding SSL to a TCP client I've written on my Android phone. I can
successfully connect to servers with properly signed certificates, and I can
connect to self-certifying hosts by cooking up a TrustManager implementation
that always thinks everything is fine.
I now have a decorator TrustManager capturing the certificates (before
delegating to its decoratee) for self-certifying hosts and presenting them for
my breathless perusal, but what I can't work out is how to implement ssh's
behaviour of warning that a host is unknown and offering to remember it for
next time - and doing so.
I presumed all I needed was to store the public key - as ssh does with
known_hosts - and re-represent it, but with this code and 'sslTrust' holding
the public key:
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null); // initialise!
ks.setKeyEntry("dbentry", Base64.decode(sslTrust, Base64.NO_WRAP), null);
tmf.init(ks);
tms = tmf.getTrustManagers();
ss.stm = new SnoopyTrustManager((X509TrustManager) tms[0]);
// ...
SLContext context = SSLContext.getInstance("SSL");
context.init(null, new TrustManager[] { ss.stm } , null);
ss.factory = context.getSocketFactory();
// ...
SocketFactory factory = ss.getFactory();
mSocket = factory.createSocket(host, port);
attempting to establish a connection results in
SSLHandshakeException: InvalidAlgorithmParameterException: trustAnchors.isEmpty()
which is fair enough: I don't know how to cook things up from the certificate
offered by the remote server. I'm also fairly sure this isn't how I tell a
TrustManager about a remote server's public key anyway.
Since the site is self-certifying, I imagine could probably just verify that
the public keys match in a trivial TrustManager, but I'd like to understand
how this 'should' be done - adding a CA on a per-connection basis, since
I won't trust that CA for anything else.
You need to use your own trust store on pre-ICS version, and add the serer's certificates to it on first error. Subsequent connections will load it from the trust store and thus trust the remote certificate. This is not a complete solution, but here's one way to do it (code on Github), along with some discussion:
http://nelenkov.blogspot.jp/2011/12/using-custom-certificate-trust-store-on.html
I write android application.
How can I use Certificate in https connection when I initialize certificate from directory file and not from packages?
When I have packages file with password, this code works:
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(certificateIs, pass.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, pass.toCharArray());
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(kmf.getKeyManagers(), trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
But I have certificate initialized from der file:
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate) cf.generateCertificate(certBytes);
I do not know how use this certificate over https connection.
You seem to be talking about client-certificate authentication (where your Android device is the client).
Firstly, you need the client to have the private key matching the public key in the certificate you're trying to use (that's the whole point, otherwise, it wouldn't authenticated anything). PKCS#12 is one of the usual formats for containing the private key and the certificate. If you only have the certificate in a der file, you probably won't have the private key in it, hence it won't work.
It's not quite clear from your question what you do with your certificate variable, with respect to the KeyManagerFactory (if you have a custom X509KeyManager, it should return the private key in its getPrivateKey method, otherwise it won't work).
Secondly, client-certificate authentication is always initiated by the server, so you'd need the server to be set up accordingly too (it seems to be the case already, if your test based on a PKCS#12 keystore works).