source: lib/ssl_gnutls.c @ 1e2094e

Last change on this file since 1e2094e was 0db6618, checked in by dequis <dx@…>, at 2015-10-26T08:28:10Z

Use proxy_disconnect() in http, ssl, jabber, oscar

Twitter and MSN are all HTTP/SSL, so they don't need it either.

The out of tree facebook and steam plugins are also covered by the
HTTP/SSL changes.

Yahoo is written in a weird way and doesn't seem to need it (it seems it
doesn't immediately stop connections when you tell it to logout)

  • Property mode set to 100644
File size: 12.2 KB
Line 
1/********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2012 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/* SSL module - GnuTLS version                                          */
8
9/*
10  This program is free software; you can redistribute it and/or modify
11  it under the terms of the GNU General Public License as published by
12  the Free Software Foundation; either version 2 of the License, or
13  (at your option) any later version.
14
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU General Public License for more details.
19
20  You should have received a copy of the GNU General Public License with
21  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
22  if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
23  Fifth Floor, Boston, MA  02110-1301  USA
24*/
25
26#include <gnutls/gnutls.h>
27#include <gnutls/x509.h>
28#include <gcrypt.h>
29#include <fcntl.h>
30#include <unistd.h>
31#include "proxy.h"
32#include "ssl_client.h"
33#include "sock.h"
34#include "stdlib.h"
35#include "bitlbee.h"
36
37int ssl_errno = 0;
38
39static gboolean initialized = FALSE;
40gnutls_certificate_credentials_t xcred;
41
42#include <limits.h>
43
44#if defined(ULONG_MAX) && ULONG_MAX > 4294967295UL
45#define GNUTLS_STUPID_CAST (long)
46#else
47#define GNUTLS_STUPID_CAST (int)
48#endif
49
50#define SSLDEBUG 0
51
52struct scd {
53        ssl_input_function func;
54        gpointer data;
55        int fd;
56        gboolean established;
57        int inpa;
58        char *hostname;
59        gboolean verify;
60
61        gnutls_session_t session;
62};
63
64static GHashTable *session_cache;
65
66static gboolean ssl_connected(gpointer data, gint source, b_input_condition cond);
67static gboolean ssl_starttls_real(gpointer data, gint source, b_input_condition cond);
68static gboolean ssl_handshake(gpointer data, gint source, b_input_condition cond);
69
70static void ssl_deinit(void);
71
72static void ssl_log(int level, const char *line)
73{
74        printf("%d %s", level, line);
75}
76
77void ssl_init(void)
78{
79        if (initialized) {
80                return;
81        }
82
83        gnutls_global_init();
84        gnutls_certificate_allocate_credentials(&xcred);
85        if (global.conf->cafile) {
86                gnutls_certificate_set_x509_trust_file(xcred, global.conf->cafile, GNUTLS_X509_FMT_PEM);
87
88                /* Not needed in GnuTLS 2.11+ (enabled by default there) so
89                   don't do it (resets possible other defaults). */
90                if (!gnutls_check_version("2.11")) {
91                        gnutls_certificate_set_verify_flags(xcred, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
92                }
93        }
94        initialized = TRUE;
95
96        gnutls_global_set_log_function(ssl_log);
97        /*
98        gnutls_global_set_log_level( 3 );
99        */
100
101        session_cache = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
102
103        atexit(ssl_deinit);
104}
105
106static void ssl_deinit(void)
107{
108        gnutls_global_deinit();
109        gnutls_certificate_free_credentials(xcred);
110        g_hash_table_destroy(session_cache);
111        session_cache = NULL;
112}
113
114void *ssl_connect(char *host, int port, gboolean verify, ssl_input_function func, gpointer data)
115{
116        struct scd *conn = g_new0(struct scd, 1);
117
118        conn->func = func;
119        conn->data = data;
120        conn->inpa = -1;
121        conn->hostname = g_strdup(host);
122        conn->verify = verify && global.conf->cafile;
123        conn->fd = proxy_connect(host, port, ssl_connected, conn);
124
125        if (conn->fd < 0) {
126                g_free(conn->hostname);
127                g_free(conn);
128                return NULL;
129        }
130
131        return conn;
132}
133
134void *ssl_starttls(int fd, char *hostname, gboolean verify, ssl_input_function func, gpointer data)
135{
136        struct scd *conn = g_new0(struct scd, 1);
137
138        conn->fd = fd;
139        conn->func = func;
140        conn->data = data;
141        conn->inpa = -1;
142        conn->hostname = g_strdup(hostname);
143
144        /* For now, SSL verification is globally enabled by setting the cafile
145           setting in bitlbee.conf. Commented out by default because probably
146           not everyone has this file in the same place and plenty of folks
147           may not have the cert of their private Jabber server in it. */
148        conn->verify = verify && global.conf->cafile;
149
150        /* This function should be called via a (short) timeout instead of
151           directly from here, because these SSL calls are *supposed* to be
152           *completely* asynchronous and not ready yet when this function
153           (or *_connect, for examle) returns. Also, errors are reported via
154           the callback function, not via this function's return value.
155
156           In short, doing things like this makes the rest of the code a lot
157           simpler. */
158
159        b_timeout_add(1, ssl_starttls_real, conn);
160
161        return conn;
162}
163
164static gboolean ssl_starttls_real(gpointer data, gint source, b_input_condition cond)
165{
166        struct scd *conn = data;
167
168        return ssl_connected(conn, conn->fd, B_EV_IO_WRITE);
169}
170
171static int verify_certificate_callback(gnutls_session_t session)
172{
173        unsigned int status;
174        const gnutls_datum_t *cert_list;
175        unsigned int cert_list_size;
176        int gnutlsret;
177        int verifyret = 0;
178        gnutls_x509_crt_t cert;
179        struct scd *conn;
180
181        conn = gnutls_session_get_ptr(session);
182
183        gnutlsret = gnutls_certificate_verify_peers2(session, &status);
184        if (gnutlsret < 0) {
185                return VERIFY_CERT_ERROR;
186        }
187
188        if (status & GNUTLS_CERT_INVALID) {
189                verifyret |= VERIFY_CERT_INVALID;
190        }
191
192        if (status & GNUTLS_CERT_REVOKED) {
193                verifyret |= VERIFY_CERT_REVOKED;
194        }
195
196        if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) {
197                verifyret |= VERIFY_CERT_SIGNER_NOT_FOUND;
198        }
199
200        if (status & GNUTLS_CERT_SIGNER_NOT_CA) {
201                verifyret |= VERIFY_CERT_SIGNER_NOT_CA;
202        }
203
204        if (status & GNUTLS_CERT_INSECURE_ALGORITHM) {
205                verifyret |= VERIFY_CERT_INSECURE_ALGORITHM;
206        }
207
208#ifdef GNUTLS_CERT_NOT_ACTIVATED
209        /* Amusingly, the GnuTLS function used above didn't check for expiry
210           until GnuTLS 2.8 or so. (See CVE-2009-1417) */
211        if (status & GNUTLS_CERT_NOT_ACTIVATED) {
212                verifyret |= VERIFY_CERT_NOT_ACTIVATED;
213        }
214
215        if (status & GNUTLS_CERT_EXPIRED) {
216                verifyret |= VERIFY_CERT_EXPIRED;
217        }
218#endif
219
220        if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509 || gnutls_x509_crt_init(&cert) < 0) {
221                return VERIFY_CERT_ERROR;
222        }
223
224        cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
225        if (cert_list == NULL || gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) {
226                return VERIFY_CERT_ERROR;
227        }
228
229        if (!gnutls_x509_crt_check_hostname(cert, conn->hostname)) {
230                verifyret |= VERIFY_CERT_INVALID;
231                verifyret |= VERIFY_CERT_WRONG_HOSTNAME;
232        }
233
234        gnutls_x509_crt_deinit(cert);
235
236        return verifyret;
237}
238
239struct ssl_session {
240        size_t size;
241        char data[];
242};
243
244static void ssl_cache_add(struct scd *conn)
245{
246        size_t data_size = 0;
247        struct ssl_session *data;
248        char *hostname;
249
250        if (!conn->hostname ||
251            gnutls_session_get_data(conn->session, NULL, &data_size) != 0) {
252                return;
253        }
254
255        data = g_malloc(sizeof(struct ssl_session) + data_size);
256        if (gnutls_session_get_data(conn->session, data->data, &data_size) != 0) {
257                g_free(data);
258                return;
259        }
260
261        hostname = g_strdup(conn->hostname);
262        g_hash_table_insert(session_cache, hostname, data);
263}
264
265static void ssl_cache_resume(struct scd *conn)
266{
267        struct ssl_session *data;
268
269        if (conn->hostname &&
270            (data = g_hash_table_lookup(session_cache, conn->hostname))) {
271                gnutls_session_set_data(conn->session, data->data, data->size);
272                g_hash_table_remove(session_cache, conn->hostname);
273        }
274}
275
276char *ssl_verify_strerror(int code)
277{
278        GString *ret = g_string_new("");
279
280        if (code & VERIFY_CERT_REVOKED) {
281                g_string_append(ret, "certificate has been revoked, ");
282        }
283        if (code & VERIFY_CERT_SIGNER_NOT_FOUND) {
284                g_string_append(ret, "certificate hasn't got a known issuer, ");
285        }
286        if (code & VERIFY_CERT_SIGNER_NOT_CA) {
287                g_string_append(ret, "certificate's issuer is not a CA, ");
288        }
289        if (code & VERIFY_CERT_INSECURE_ALGORITHM) {
290                g_string_append(ret, "certificate uses an insecure algorithm, ");
291        }
292        if (code & VERIFY_CERT_NOT_ACTIVATED) {
293                g_string_append(ret, "certificate has not been activated, ");
294        }
295        if (code & VERIFY_CERT_EXPIRED) {
296                g_string_append(ret, "certificate has expired, ");
297        }
298        if (code & VERIFY_CERT_WRONG_HOSTNAME) {
299                g_string_append(ret, "certificate hostname mismatch, ");
300        }
301
302        if (ret->len == 0) {
303                g_string_free(ret, TRUE);
304                return NULL;
305        } else {
306                g_string_truncate(ret, ret->len - 2);
307                return g_string_free(ret, FALSE);
308        }
309}
310
311static gboolean ssl_connected(gpointer data, gint source, b_input_condition cond)
312{
313        struct scd *conn = data;
314
315        if (source == -1) {
316                conn->func(conn->data, 0, NULL, cond);
317                g_free(conn->hostname);
318                g_free(conn);
319                return FALSE;
320        }
321
322        ssl_init();
323
324        gnutls_init(&conn->session, GNUTLS_CLIENT);
325        gnutls_session_set_ptr(conn->session, (void *) conn);
326#if GNUTLS_VERSION_NUMBER < 0x020c00
327        gnutls_transport_set_lowat(conn->session, 0);
328#endif
329        gnutls_set_default_priority(conn->session);
330        gnutls_credentials_set(conn->session, GNUTLS_CRD_CERTIFICATE, xcred);
331        if (conn->hostname && !g_ascii_isdigit(conn->hostname[0])) {
332                gnutls_server_name_set(conn->session, GNUTLS_NAME_DNS,
333                                       conn->hostname, strlen(conn->hostname));
334        }
335
336        sock_make_nonblocking(conn->fd);
337        gnutls_transport_set_ptr(conn->session, (gnutls_transport_ptr_t) GNUTLS_STUPID_CAST conn->fd);
338
339        ssl_cache_resume(conn);
340
341        return ssl_handshake(data, source, cond);
342}
343
344static gboolean ssl_handshake(gpointer data, gint source, b_input_condition cond)
345{
346        struct scd *conn = data;
347        int st, stver;
348
349        /* This function returns false, so avoid calling b_event_remove again */
350        conn->inpa = -1;
351
352        if ((st = gnutls_handshake(conn->session)) < 0) {
353                if (st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED) {
354                        conn->inpa = b_input_add(conn->fd, ssl_getdirection(conn),
355                                                 ssl_handshake, data);
356                } else {
357                        conn->func(conn->data, 0, NULL, cond);
358
359                        ssl_disconnect(conn);
360                }
361        } else {
362                if (conn->verify && (stver = verify_certificate_callback(conn->session)) != 0) {
363                        conn->func(conn->data, stver, NULL, cond);
364
365                        ssl_disconnect(conn);
366                } else {
367                        /* For now we can't handle non-blocking perfectly everywhere... */
368                        sock_make_blocking(conn->fd);
369
370                        ssl_cache_add(conn);
371                        conn->established = TRUE;
372                        conn->func(conn->data, 0, conn, cond);
373                }
374        }
375
376        return FALSE;
377}
378
379int ssl_read(void *conn, char *buf, int len)
380{
381        int st;
382
383        if (!((struct scd*) conn)->established) {
384                ssl_errno = SSL_NOHANDSHAKE;
385                return -1;
386        }
387
388        st = gnutls_record_recv(((struct scd*) conn)->session, buf, len);
389
390        ssl_errno = SSL_OK;
391        if (st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED) {
392                ssl_errno = SSL_AGAIN;
393        }
394
395        if (SSLDEBUG && getenv("BITLBEE_DEBUG") && st > 0) {
396                len = write(2, buf, st);
397        }
398
399        return st;
400}
401
402int ssl_write(void *conn, const char *buf, int len)
403{
404        int st;
405
406        if (!((struct scd*) conn)->established) {
407                ssl_errno = SSL_NOHANDSHAKE;
408                return -1;
409        }
410
411        st = gnutls_record_send(((struct scd*) conn)->session, buf, len);
412
413        ssl_errno = SSL_OK;
414        if (st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED) {
415                ssl_errno = SSL_AGAIN;
416        }
417
418        if (SSLDEBUG && getenv("BITLBEE_DEBUG") && st > 0) {
419                len = write(2, buf, st);
420        }
421
422        return st;
423}
424
425int ssl_pending(void *conn)
426{
427        if (conn == NULL) {
428                return 0;
429        }
430
431        if (!((struct scd*) conn)->established) {
432                ssl_errno = SSL_NOHANDSHAKE;
433                return 0;
434        }
435
436#if GNUTLS_VERSION_NUMBER >= 0x03000d && GNUTLS_VERSION_NUMBER <= 0x030012
437        if (ssl_errno == SSL_AGAIN) {
438                return 0;
439        }
440#endif
441
442        return gnutls_record_check_pending(((struct scd*) conn)->session) != 0;
443}
444
445void ssl_disconnect(void *conn_)
446{
447        struct scd *conn = conn_;
448
449        if (conn->inpa != -1) {
450                b_event_remove(conn->inpa);
451        }
452
453        if (conn->established) {
454                gnutls_bye(conn->session, GNUTLS_SHUT_WR);
455        }
456
457        proxy_disconnect(conn->fd);
458
459        if (conn->session) {
460                gnutls_deinit(conn->session);
461        }
462        g_free(conn->hostname);
463        g_free(conn);
464}
465
466int ssl_getfd(void *conn)
467{
468        return(((struct scd*) conn)->fd);
469}
470
471b_input_condition ssl_getdirection(void *conn)
472{
473        return(gnutls_record_get_direction(((struct scd*) conn)->session) ?
474               B_EV_IO_WRITE : B_EV_IO_READ);
475}
476
477size_t ssl_des3_encrypt(const unsigned char *key, size_t key_len, const unsigned char *input,
478                        size_t input_len, const unsigned char *iv, unsigned char **res)
479{
480        gcry_cipher_hd_t gcr;
481        gcry_error_t st;
482
483        ssl_init();
484
485        *res = g_malloc(input_len);
486        st = gcry_cipher_open(&gcr, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_CBC, 0) ||
487             gcry_cipher_setkey(gcr, key, key_len) ||
488             gcry_cipher_setiv(gcr, iv, 8) ||
489             gcry_cipher_encrypt(gcr, *res, input_len, input, input_len);
490
491        gcry_cipher_close(gcr);
492
493        if (st == 0) {
494                return input_len;
495        }
496
497        g_free(*res);
498        return 0;
499}
Note: See TracBrowser for help on using the repository browser.