简体   繁体   中英

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.

ps 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?

The <<22,3,1... packet is a TLS handshake packet. It's not actually XML; it's printed as individual bytes in decimal. The 22 byte means "handshake", and 3 and 1 are the major and minor version numbers, respectively. Version "3.1" actually stands for TLS 1.0. See the description on Wikipedia for more details.

What seems to happen is that your Java code initiates the TLS handshake right after connecting, but ejabberd expects it to first negotiate STARTTLS. This is described in section 5 of RFC 6120 . Basically, the server sends a list of features, including STARTTLS:

<stream:features>
   <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'>
     <required/>
   </starttls>
</stream:features>

And the client asks for STARTTLS:

<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>

And the server tells the client to proceed:

<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>

And then the client can start the TLS handshake.


There must be a way to make Smack do the above STARTTLS negotiation, but I don't know how. However, you could make ejabberd accept this style of TLS handshake by changing the configuration:

port: 5223
starttls: false

Traditionally XMPP servers accept "normal" connections that do STARTTLS on port 5222, and "instant-TLS" connections on port 5223, so I suggest following that convention in order to reduce confusion.

不要设置插座工厂。

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'/>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM