Validation of a certificate chain when opening a SSLSocket in Java - 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.

Related

How to establish TLS connection between stunnel and Android app with my own certs from my CA

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.

One-Way SSL Authentication as using Netty

I'm trying to build a one-way authentication socket server using Netty.
First I used keytool to generate keystore, self signed certificate, truststore for both server and client, and I wrote some code in my server/client, the SSL authentication is working.
Here is my question:
Is there any way that I don't need to add truststore to my client, I only add the keystore to my server, and it would still work well? I thought one-way authentication means that only server holds the certificate?
The following is what I wrote in my server/client to add the SslHandler so far:
server:
private void addSslHandlerOneWay(SocketChannel ch) throws Exception {
SSLContext sslContext = SSLContext.getInstance("TLS");
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(new File("svrks.jks")), "kspassword1".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, "kspassword2".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ks);
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
SSLEngine sslEngine = sslContext.createSSLEngine();
sslEngine.setUseClientMode(false);
sslEngine.setNeedClientAuth(false);//one-way
sslEngine.setEnabledProtocols(sslEngine.getSupportedProtocols());
sslEngine.setEnabledCipherSuites(sslEngine.getSupportedCipherSuites());
sslEngine.setEnableSessionCreation(true);
ch.pipeline().addFirst("SSL", new SslHandler(sslEngine));
}
client:
private void addSslHandlerOneWay(SocketChannel ch) throws Exception {
SSLContext sslContext = SSLContext.getInstance("TLS");
KeyStore ts = KeyStore.getInstance("JKS");
ts.load(getInputStream("clits.jks"), "tspassword2".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ts, "tspassword1".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ts);
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
SSLEngine sslEngine = sslContext.createSSLEngine();
sslEngine.setUseClientMode(true);//client
sslEngine.setEnabledProtocols(sslEngine.getSupportedProtocols());
sslEngine.setEnabledCipherSuites(sslEngine.getSupportedCipherSuites());
sslEngine.setEnableSessionCreation(true);
ch.pipeline().addFirst("SSL", new SslHandler(sslEngine));
}
Thanks, guys.
I thought one-way authentication means that only server holds the certificate?
The server needs to authenticate itself with the certificate. For this it needs certificate and matching private key.
The client needs to verify the authentication, i.e. that the certificate send by the server is actually the expected one. For this it needs to know either the certificate itself or the CA which issued the certificate - which is the same in case of self-signed certificates.
What you seem to expect is that the client does not need any previous knowledge of the servers certificate or the issuer CA. If this would be the case then the client would just accept any certificate, both corrects ones from the server and also fake ones from an attacker. Without previous knowledge (i.e. a local trust anchor) what to expect the server cannot distinguish between correct and fake certificates.

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!

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.

How can I effect 'known_hosts' vs self-certified servers for my app's SSL usage?

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

Categories

Resources