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.
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.
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.
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.
I have a java chat app using TLS, but when I use Wireshark to capture data the protocol column display value "TCP", why does it not show as "TLS". What's happen with my code?
CertificateFactory cf = CertificateFactory.getInstance("X509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(
this.getClass().getResourceAsStream("/"+"lib/ca.crt")
);
InputStream is=this.getClass().getResourceAsStream("/"+"lib/plainclient.jks");
KeyStore clientKeys = KeyStore.getInstance("JKS");
clientKeys.load(is,"xuanthinh".toCharArray());
KeyManagerFactory clientKeyManager = KeyManagerFactory.getInstance("SunX509");
clientKeyManager.init(clientKeys,"xuanthinh".toCharArray());
KeyStore ks = KeyStore.getInstance("JKS");
is=this.getClass().getResourceAsStream("/"+"lib/serverpub.jks");
ks.load(is,"xuanthinh".toCharArray());
TrustManagerFactory trustManager = TrustManagerFactory.getInstance("SunX509");
trustManager.init(ks);
//use keys to create SSLSoket
ssl = SSLContext.getInstance("TLS");
ssl.init(
clientKeyManager.getKeyManagers(), trustManager.getTrustManagers(),
SecureRandom.getInstance("SHA1PRNG")
);
They are different protocols, and both are used when transferring encrypted content.
Have a read on the OSI Model for network protocols as this explains where each protocol sits relative to each other.
TCP is part of the Layer 4 (Transport Layer), whilst TLS is part of Layer 6 (Presentation Layer).
Not sure what client you're using from the snippet. But doing this with Jersey/CXF i usually just set the http.protocols value. This link has some helpful information: https://superuser.com/questions/747377/enable-tls-1-1-and-1-2-for-clients-on-java-7
I'm trying to implement a Java SSL client that will work with my C++ SSL server. I know the SSL server works as I've tested it with an CURL SSL client.
The problem I'm seeing when I try to connect with the Java client is it doesn't seem to be sending the client certificate, so the server throws an error saying it didn't receive a client cert.
I seem to be loading the client cert and key so I am puzzled why the server is not receiving ANY cert.
Would appreciate someone looking at my Java client code and seeing if anything jumps out at you.
KeyStore keyStore = KeyStore.getInstance("JKS");
InputStream stream = null;
stream = new FileInputStream("C:\\certs\\client.keystore");
keyStore.load(stream, "changeit".toCharArray());
System.out.println("loaded the keystore");
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
PrivateKey key = (PrivateKey) keyStore.getKey("client-identity", "changeit".toCharArray());
System.out.println("private key: " + key);
Certificate cert = (Certificate) keyStore.getCertificate("client-identity");
System.out.println("client cert: " + cert);
kmf.init(keyStore, "changeit".toCharArray());
SSLContext sslCtx = SSLContext.getInstance("TLS");
sslCtx.init(kmf.getKeyManagers(), null, null);
SSLSocketFactory sslSF = (SSLSocketFactory) sslCtx.getSocketFactory();
System.out.println(" Creating and opening new SSLSocket with SSLSocketFactory");
// using createSocket(String hostname, int port)
SSLSocket sslSock = (SSLSocket) sslSF.createSocket("localhost", 1111);
System.out.println(" SSLSocket created");
HandshakeCompletedListener mListener = null;
mListener = new MyListener();
sslSock.addHandshakeCompletedListener(new MyListener());
Update
This might be the issue.
C:\movrm-contract\https-client>keytool -list -keystore client.keystore -v
keytool error: java.lang.Exception: Keystore file does not exist: client.keystore
java.lang.Exception: Keystore file does not exist: client.keystore
at sun.security.tools.KeyTool.doCommands(KeyTool.java:565)
at sun.security.tools.KeyTool.run(KeyTool.java:171)
at sun.security.tools.KeyTool.main(KeyTool.java:165)
But I'm not sure how to fix this. I created the JSK file from client.pem and privkey.pem files. What is the right way to import them into a Java keystore? And, does the signing CA cert need to be in that same key store?