Monday, February 16, 2015

Google Talk ends its services, now what?

Due to the decision Google made to turn off its xmpp service talk, I've been forced to migrate another xmpp service.
I thought it would be an easy task, just put new data into xmpp.conf and it's done. But it didn't go that way.
As soon as I run the new configuration, I got:

<--- XMPP sent to 'tigase.im' --->
<?xml version='1.0'?><stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client' to='tigase.im' version='1.0'>
<------------->

<--- XMPP received from 'tigase.im' --->
<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' from='tigase.im' id='6be9205d-5793-4ae6-84e0-afd0983ed490' version='1.0' xml:lang='en'>
<------------->

<--- XMPP sent to 'tigase.im' --->
<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
<------------->

<--- XMPP received from 'tigase.im' --->
<stream:features><sm xmlns="urn:xmpp:sm:3"/><register xmlns="http://jabber.org/features/iq-register"/><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/><ver xmlns="urn:xmpp:features:rosterver"/><starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"><required/></starttls><compression xmlns="http://jabber.org/features/compress"><method>zlib</method></compression></stream:features>
<------------->

<--- XMPP received from 'tigase.im' --->
<proceed xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>
<------------->
[Feb 16 19:20:56] ERROR[20733]: res_xmpp.c:2558 xmpp_client_requested_tls: TLS connection for client 'tigase.im' cannot be established. OpenSSL initialization failed.
[Feb 16 19:20:56] WARNING[20733]: res_xmpp.c:3571 xmpp_client_receive: Parsing failure: Hook returned an error.
[Feb 16 19:20:56] WARNING[20733]: res_xmpp.c:3635 xmpp_client_thread: JABBER: socket read error

What the hell was there?
I reviewed all the res_xmpp parameters, but at last I had to accept to the fact it doesn't work.
A xmpp provider related issue?
Might be, but changing to another xmpp service didn't help, and connection kept fail.

Debug log states it is a TLS connection related problem...
I started my investigation on iksemel, the xmpp parser library.
It has its own tls mechanism based on gnutls, which is independent with the one implemented in res_xmpp, but at that time I didn't know yet.

There is a tool in the "tool" directory of the iksemel library which performs a simple xmpp connection.
It is the iksroster.
Using that to test the xmpp connection I got a TSL connection error.

Looking at the code, the I found in the src/stream.c file that function handshake performs the negotiation for the TLS connection:

static int handshake (struct stream_data *data)
{
        const int protocol_priority[] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 };
        const int kx_priority[] = { GNUTLS_KX_RSA, 0 };
        const int cipher_priority[] = { GNUTLS_CIPHER_3DES_CBC, GNUTLS_CIPHER_ARCFOUR, 0};
        const int comp_priority[] = { GNUTLS_COMP_ZLIB, GNUTLS_COMP_NULL, 0 };
        const int mac_priority[] = { GNUTLS_MAC_SHA, GNUTLS_MAC_MD5, 0 };
        int ret;

        if (gnutls_global_init () != 0) return IKS_NOMEM;              
        if (gnutls_certificate_allocate_credentials (&data->cred) < 0) return IKS_NOMEM;       
        if (gnutls_init (&data->sess, GNUTLS_CLIENT) != 0) {
                                                            gnutls_certificate_free_credentials (data->cred);
                                                            return IKS_NOMEM;
                                                           }
        gnutls_protocol_set_priority (data->sess, protocol_priority);
        gnutls_cipher_set_priority(data->sess, cipher_priority);
        gnutls_compression_set_priority(data->sess, comp_priority);
        gnutls_kx_set_priority(data->sess, kx_priority);
        gnutls_mac_set_priority(data->sess, mac_priority);
        gnutls_credentials_set (data->sess, GNUTLS_CRD_CERTIFICATE, data->cred);

        gnutls_transport_set_push_function (data->sess, (gnutls_push_func) tls_push);
        gnutls_transport_set_pull_function (data->sess, (gnutls_pull_func) tls_pull);
        gnutls_transport_set_ptr (data->sess, data->prs);

        ret = gnutls_handshake (data->sess);
        if (ret != 0) {
                       gnutls_deinit (data->sess);
                       gnutls_certificate_free_credentials (data->cred);
                       return IKS_NET_TLSFAIL;
                      }
        data->flags &= (~SF_TRY_SECURE);
        data->flags |= SF_SECURE;
        iks_send_header (data->prs, data->server);
        return IKS_OK;
}

The code shows the function is using a deprecated chiper, it could be the reason why TLS connection fails.
A test that worth to try is add another more recent cipher.
const int cipher_priority[] = { GNUTLS_CIPHER_AES_256_CBC,GNUTLS_CIPHER_AES_128_CBC,GNUTLS_CIPHER_3DES_CBC, GNUTLS_CIPHER_ARCFOUR, 0};
Indeed The new version made the trick, and that time on, iksroster successfully connected to the server.
In the asterisk console however, no change.
Looking with attention to the res_xmpp.c code, I finally got the problem: asterisk does its own TLS connection using OpenSSL, and it is bound to use SSLv3.
You can be more lucky than me, but it seems not many xmpp servers are using SSLv3!
You can test yourself  you xmpp provider using the tools provided by https://xmpp.net/, in my case, tigase.im used TLSv1.2.
The only way I found to make my connection work has been patch my res_xmpp.c to use TLSv1.1

static int xmpp_client_requested_tls(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg, int type, iks *node)
{
#ifdef HAVE_OPENSSL
        int sock;
#endif
        int dbg_info=0;

        if (!strcmp(iks_name(node), "success")) {
                /* TLS is up and working, we can move on to authenticating now */
                xmpp_client_change_state(client, XMPP_STATE_AUTHENTICATE);
                return 0;
        } else if (!strcmp(iks_name(node), "failure")) {
                /* TLS negotiation was a failure, close it on down! */
                return -1;
        } else if (strcmp(iks_name(node), "proceed")) {
                /* Ignore any other responses */
                return 0;
        }

#ifndef HAVE_OPENSSL
        ast_log(LOG_ERROR, "Somehow we managed to try to start TLS negotiation on client '%s' without OpenSSL support, disconnectin$
        return -1;
#else
//        client->ssl_method = SSLv3_method();  //this was the original
        client->ssl_method = TLSv1_1_method();
        if (!(client->ssl_context = SSL_CTX_new((SSL_METHOD *) client->ssl_method))) {
                dbg_info=1;
                goto failure;
        }

        if (!(client->ssl_session = SSL_new(client->ssl_context))) {
                dbg_info=2;
                goto failure;
        }

        sock = iks_fd(client->parser);
        if (!SSL_set_fd(client->ssl_session, sock)) {
                dbg_info=3;
                goto failure;
        }

        if (!SSL_connect(client->ssl_session)) {
                dbg_info=4;
                goto failure;
        }

Now my connection works.

I hope you find this useful.