MattC MattC - 5 months ago 432
Android Question

Android Smack SSL/TLS connection to XMPP Ejabberd server with CA Certificate

i'm developing my first XMPP Android application, i've not a lot practice in XMPP but actually i'm able to connect my Smack client to my Ejabberd server successfully, the problem comes out when i try to do the same using TLS (with CA Certificate).

Here the piece of ejabberd.yml config about TLS:

hosts:
- "localhost"
- "mydomain.com"

listen:
-
port: 5222
module: ejabberd_c2s
##
## If TLS is compiled in and you installed a SSL
## certificate, specify the full path to the
## file and uncomment these lines:
##
certfile: "/home/matt/ssl-cert/stunnel.pem"
starttls: true


The pem file must be valid because i use it for a SSL WebSocket connection without problems.

Here the methods used in my XMPP Java class to initialise the TLS connection:

private void initialiseConnection() {

XMPPTCPConnectionConfiguration.Builder config = XMPPTCPConnectionConfiguration
.builder();
config.setSecurityMode(ConnectionConfiguration.SecurityMode.ifpossible);
config.setServiceName(serverAddress); //mydomain.com
config.setHost(serverAddress);
config.setPort(5222);
config.setDebuggerEnabled(true);

SSLContext sslContext = null;

try {
sslContext = createSSLContext(context);
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
}

config.setCustomSSLContext(sslContext);
config.setSocketFactory(sslContext.getSocketFactory());

XMPPTCPConnection.setUseStreamManagementResumptiodDefault(true);
XMPPTCPConnection.setUseStreamManagementDefault(true);

connection = new XMPPTCPConnection(config.build());
XMPPConnectionListener connectionListener = new XMPPConnectionListener();
connection.addConnectionListener(connectionListener);
}

private SSLContext createSSLContext(Context context) throws KeyStoreException,
NoSuchAlgorithmException, KeyManagementException, IOException, CertificateException {

KeyStore trustStore;
InputStream in = null;
trustStore = KeyStore.getInstance("BKS");

in = context.getResources().openRawResource(R.raw.my_keystore);

trustStore.load(in, "MyPassword123".toCharArray());

TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
return sslContext;
}


Note that without the SSL/TLS parts (and without the SSL/TLS parts in Ejabberd config) everything works.

p.s For the keystore creation and the SSL methods integration i've followed the lqbal tutorial in this page.

Now, the Android Monitor log (Android Studio), gives me only one row about the connection problem.

E/(onCreate): IOException: Handshake failed


And no more, but on the Ejabberd server log i've the following rows:

2016-06-14 15:57:26.461 [info] <0.14993.0>@ejabberd_listener:accept:333 (#Port<0.73878>) Accepted connection xx.xx.xx.xx:xxxxx -> xx.xxx.xx.xx:5222
2016-06-14 15:57:26.466 [debug] <0.15099.0>@ejabberd_receiver:process_data:284 Received XML on stream = <<22,3,1,0,133,1,0,0,129,3,3,159,29,211,249,221,88,135,177,183,150,98,234,76,6,91,52,30,26,186,202,176,199,127,245,56,211,198,43,66,35,237,140,0,0,40,192,43,192,44,192,47,192,48,0,158,0,159,192,9,192,10,192,19,192,20,0,51,0,57,192,7,192,17,0,156,0,157,0,47,0,53,0,5,0,255,1,0,0,48,0,23,0,0,0,13,0,22,0,20,6,1,6,3,5,1,5,3,4,1,4,3,3,1,3,3,2,1,2,3,0,11,0,2,1,0,0,10,0,8,0,6,0,23,0,24,0,25>>
2016-06-14 15:57:26.466 [debug] <0.15100.0>@ejabberd_c2s:send_text:1832 Send XML on stream = <<"<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='17298480576042278904' from='mydomain.com' version='1.0'>">>
2016-06-14 15:57:26.466 [debug] <0.15100.0>@ejabberd_c2s:send_text:1832 Send XML on stream = <<"<stream:error><xml-not-well-formed xmlns='urn:ietf:params:xml:ns:xmpp-streams'></xml-not-well-formed></stream:error>">>
2016-06-14 15:57:26.466 [debug] <0.15100.0>@ejabberd_c2s:send_text:1832 Send XML on stream = <<"</stream:stream>">>


I can't understand this received "<<22,3,1..." tag (???)

What's wrong?

Answer

Ok, finally after some research and thanks to the previous answers i'm able to connect my Smack client to my Ejabberd server. Here below all the edits.

This is the XMPP class final code, i've removed the config.setSocketFactory row and changed the connection Port to 5223.

private void initialiseConnection() {

    XMPPTCPConnectionConfiguration.Builder config = XMPPTCPConnectionConfiguration
            .builder();
    config.setSecurityMode(ConnectionConfiguration.SecurityMode.ifpossible);
    config.setServiceName(serverAddress);
    config.setHost(serverAddress);
    config.setPort(5223);
    config.setDebuggerEnabled(true);

    SSLContext sslContext = null;

    try {
        sslContext = createSSLContext(context);
    } catch (KeyStoreException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (KeyManagementException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (CertificateException e) {
        e.printStackTrace();
    }

    config.setCustomSSLContext(sslContext);

    XMPPTCPConnection.setUseStreamManagementResumptiodDefault(true);
    XMPPTCPConnection.setUseStreamManagementDefault(true);

    connection = new XMPPTCPConnection(config.build());
    XMPPConnectionListener connectionListener = new XMPPConnectionListener();
    connection.addConnectionListener(connectionListener);
}

private SSLContext createSSLContext(Context context) throws KeyStoreException,
        NoSuchAlgorithmException, KeyManagementException, IOException, CertificateException {

    KeyStore trustStore;
    InputStream in = null;
    trustStore = KeyStore.getInstance("BKS");

    in = context.getResources().openRawResource(R.raw.my_keystore);

    trustStore.load(in, "MyPassword123".toCharArray());

    TrustManagerFactory trustManagerFactory = TrustManagerFactory
            .getInstance(KeyManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init(trustStore);
    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
    return sslContext;
}

These are the new port settings in the ejabberd.yml file

port: 5223
module: ejabberd_c2s
certfile: "/etc/ejabberd/ejabberd.pem"
starttls: true

And here the certificates, i was wrong on both the client and the server, for the client part i've followed the lqbal tutorial on this page, with the Step 2 and 3 i was able to create a keystore file and verify it, but i used the wrong certificate file, the right one is the External CA Root cert ("AddTrustExternalCARoot.crt" COMODO in my case).

For the server i've used a pem chain with a wrong placement of the certs inside, the correct way for the ejabberd.pem is the following (you can find all the details here): 1. Private Key (.key) 2. Certificate (domain.crt) 3. Chains (.ca-bundle). At last i've moved the ejabberd.pem in the /etc/ejabberd folder.

Now the TLS connection works:

SMACK: SENT (0): <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'></starttls>
SMACK: RECV (0): <proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>