source: lib/ssl_gnutls.c @ 5ebff60

Last change on this file since 5ebff60 was 5ebff60, checked in by dequis <dx@…>, at 2015-02-20T22:50:54Z

Reindent everything to K&R style with tabs

Used uncrustify, with the configuration file in ./doc/uncrustify.cfg

Commit author set to "Indent <please@…>" so that it's easier to
skip while doing git blame.

  • Property mode set to 100644
File size: 12.3 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);
127                return NULL;
128        }
129
130        return conn;
131}
132
133void *ssl_starttls(int fd, char *hostname, gboolean verify, ssl_input_function func, gpointer data)
134{
135        struct scd *conn = g_new0(struct scd, 1);
136
137        conn->fd = fd;
138        conn->func = func;
139        conn->data = data;
140        conn->inpa = -1;
141        conn->hostname = g_strdup(hostname);
142
143        /* For now, SSL verification is globally enabled by setting the cafile
144           setting in bitlbee.conf. Commented out by default because probably
145           not everyone has this file in the same place and plenty of folks
146           may not have the cert of their private Jabber server in it. */
147        conn->verify = verify && global.conf->cafile;
148
149        /* This function should be called via a (short) timeout instead of
150           directly from here, because these SSL calls are *supposed* to be
151           *completely* asynchronous and not ready yet when this function
152           (or *_connect, for examle) returns. Also, errors are reported via
153           the callback function, not via this function's return value.
154
155           In short, doing things like this makes the rest of the code a lot
156           simpler. */
157
158        b_timeout_add(1, ssl_starttls_real, conn);
159
160        return conn;
161}
162
163static gboolean ssl_starttls_real(gpointer data, gint source, b_input_condition cond)
164{
165        struct scd *conn = data;
166
167        return ssl_connected(conn, conn->fd, B_EV_IO_WRITE);
168}
169
170static int verify_certificate_callback(gnutls_session_t session)
171{
172        unsigned int status;
173        const gnutls_datum_t *cert_list;
174        unsigned int cert_list_size;
175        int gnutlsret;
176        int verifyret = 0;
177        gnutls_x509_crt_t cert;
178        struct scd *conn;
179
180        conn = gnutls_session_get_ptr(session);
181
182        gnutlsret = gnutls_certificate_verify_peers2(session, &status);
183        if (gnutlsret < 0) {
184                return VERIFY_CERT_ERROR;
185        }
186
187        if (status & GNUTLS_CERT_INVALID) {
188                verifyret |= VERIFY_CERT_INVALID;
189        }
190
191        if (status & GNUTLS_CERT_REVOKED) {
192                verifyret |= VERIFY_CERT_REVOKED;
193        }
194
195        if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) {
196                verifyret |= VERIFY_CERT_SIGNER_NOT_FOUND;
197        }
198
199        if (status & GNUTLS_CERT_SIGNER_NOT_CA) {
200                verifyret |= VERIFY_CERT_SIGNER_NOT_CA;
201        }
202
203        if (status & GNUTLS_CERT_INSECURE_ALGORITHM) {
204                verifyret |= VERIFY_CERT_INSECURE_ALGORITHM;
205        }
206
207#ifdef GNUTLS_CERT_NOT_ACTIVATED
208        /* Amusingly, the GnuTLS function used above didn't check for expiry
209           until GnuTLS 2.8 or so. (See CVE-2009-1417) */
210        if (status & GNUTLS_CERT_NOT_ACTIVATED) {
211                verifyret |= VERIFY_CERT_NOT_ACTIVATED;
212        }
213
214        if (status & GNUTLS_CERT_EXPIRED) {
215                verifyret |= VERIFY_CERT_EXPIRED;
216        }
217#endif
218
219        if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509 || gnutls_x509_crt_init(&cert) < 0) {
220                return VERIFY_CERT_ERROR;
221        }
222
223        cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
224        if (cert_list == NULL || gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) {
225                return VERIFY_CERT_ERROR;
226        }
227
228        if (!gnutls_x509_crt_check_hostname(cert, conn->hostname)) {
229                verifyret |= VERIFY_CERT_INVALID;
230                verifyret |= VERIFY_CERT_WRONG_HOSTNAME;
231        }
232
233        gnutls_x509_crt_deinit(cert);
234
235        return verifyret;
236}
237
238struct ssl_session {
239        size_t size;
240        char data[];
241};
242
243static void ssl_cache_add(struct scd *conn)
244{
245        size_t data_size = 0;
246        struct ssl_session *data;
247        char *hostname;
248
249        if (!conn->hostname ||
250            gnutls_session_get_data(conn->session, NULL, &data_size) != 0) {
251                return;
252        }
253
254        data = g_malloc(sizeof(struct ssl_session) + data_size);
255        if (gnutls_session_get_data(conn->session, data->data, &data_size) != 0) {
256                g_free(data);
257                return;
258        }
259
260        hostname = g_strdup(conn->hostname);
261        g_hash_table_insert(session_cache, hostname, data);
262}
263
264static void ssl_cache_resume(struct scd *conn)
265{
266        struct ssl_session *data;
267
268        if (conn->hostname &&
269            (data = g_hash_table_lookup(session_cache, conn->hostname))) {
270                gnutls_session_set_data(conn->session, data->data, data->size);
271                g_hash_table_remove(session_cache, conn->hostname);
272        }
273}
274
275char *ssl_verify_strerror(int code)
276{
277        GString *ret = g_string_new("");
278
279        if (code & VERIFY_CERT_REVOKED) {
280                g_string_append(ret, "certificate has been revoked, ");
281        }
282        if (code & VERIFY_CERT_SIGNER_NOT_FOUND) {
283                g_string_append(ret, "certificate hasn't got a known issuer, ");
284        }
285        if (code & VERIFY_CERT_SIGNER_NOT_CA) {
286                g_string_append(ret, "certificate's issuer is not a CA, ");
287        }
288        if (code & VERIFY_CERT_INSECURE_ALGORITHM) {
289                g_string_append(ret, "certificate uses an insecure algorithm, ");
290        }
291        if (code & VERIFY_CERT_NOT_ACTIVATED) {
292                g_string_append(ret, "certificate has not been activated, ");
293        }
294        if (code & VERIFY_CERT_EXPIRED) {
295                g_string_append(ret, "certificate has expired, ");
296        }
297        if (code & VERIFY_CERT_WRONG_HOSTNAME) {
298                g_string_append(ret, "certificate hostname mismatch, ");
299        }
300
301        if (ret->len == 0) {
302                g_string_free(ret, TRUE);
303                return NULL;
304        } else {
305                g_string_truncate(ret, ret->len - 2);
306                return g_string_free(ret, FALSE);
307        }
308}
309
310static gboolean ssl_connected(gpointer data, gint source, b_input_condition cond)
311{
312        struct scd *conn = data;
313
314        if (source == -1) {
315                conn->func(conn->data, 0, NULL, cond);
316                g_free(conn);
317                return FALSE;
318        }
319
320        ssl_init();
321
322        gnutls_init(&conn->session, GNUTLS_CLIENT);
323        gnutls_session_set_ptr(conn->session, (void *) conn);
324#if GNUTLS_VERSION_NUMBER < 0x020c00
325        gnutls_transport_set_lowat(conn->session, 0);
326#endif
327        gnutls_set_default_priority(conn->session);
328        gnutls_credentials_set(conn->session, GNUTLS_CRD_CERTIFICATE, xcred);
329        if (conn->hostname && !g_ascii_isdigit(conn->hostname[0])) {
330                gnutls_server_name_set(conn->session, GNUTLS_NAME_DNS,
331                                       conn->hostname, strlen(conn->hostname));
332        }
333
334        sock_make_nonblocking(conn->fd);
335        gnutls_transport_set_ptr(conn->session, (gnutls_transport_ptr_t) GNUTLS_STUPID_CAST conn->fd);
336
337        ssl_cache_resume(conn);
338
339        return ssl_handshake(data, source, cond);
340}
341
342static gboolean ssl_handshake(gpointer data, gint source, b_input_condition cond)
343{
344        struct scd *conn = data;
345        int st, stver;
346
347        /* This function returns false, so avoid calling b_event_remove again */
348        conn->inpa = -1;
349
350        if ((st = gnutls_handshake(conn->session)) < 0) {
351                if (st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED) {
352                        conn->inpa = b_input_add(conn->fd, ssl_getdirection(conn),
353                                                 ssl_handshake, data);
354                } else {
355                        conn->func(conn->data, 0, NULL, cond);
356
357                        gnutls_deinit(conn->session);
358                        closesocket(conn->fd);
359
360                        g_free(conn);
361                }
362        } else {
363                if (conn->verify && (stver = verify_certificate_callback(conn->session)) != 0) {
364                        conn->func(conn->data, stver, NULL, cond);
365
366                        gnutls_deinit(conn->session);
367                        closesocket(conn->fd);
368
369                        g_free(conn);
370                } else {
371                        /* For now we can't handle non-blocking perfectly everywhere... */
372                        sock_make_blocking(conn->fd);
373
374                        ssl_cache_add(conn);
375                        conn->established = TRUE;
376                        conn->func(conn->data, 0, conn, cond);
377                }
378        }
379
380        return FALSE;
381}
382
383int ssl_read(void *conn, char *buf, int len)
384{
385        int st;
386
387        if (!((struct scd*) conn)->established) {
388                ssl_errno = SSL_NOHANDSHAKE;
389                return -1;
390        }
391
392        st = gnutls_record_recv(((struct scd*) conn)->session, buf, len);
393
394        ssl_errno = SSL_OK;
395        if (st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED) {
396                ssl_errno = SSL_AGAIN;
397        }
398
399        if (SSLDEBUG && getenv("BITLBEE_DEBUG") && st > 0) {
400                len = write(2, buf, st);
401        }
402
403        return st;
404}
405
406int ssl_write(void *conn, const char *buf, int len)
407{
408        int st;
409
410        if (!((struct scd*) conn)->established) {
411                ssl_errno = SSL_NOHANDSHAKE;
412                return -1;
413        }
414
415        st = gnutls_record_send(((struct scd*) conn)->session, buf, len);
416
417        ssl_errno = SSL_OK;
418        if (st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED) {
419                ssl_errno = SSL_AGAIN;
420        }
421
422        if (SSLDEBUG && getenv("BITLBEE_DEBUG") && st > 0) {
423                len = write(2, buf, st);
424        }
425
426        return st;
427}
428
429int ssl_pending(void *conn)
430{
431        if (conn == NULL) {
432                return 0;
433        }
434
435        if (!((struct scd*) conn)->established) {
436                ssl_errno = SSL_NOHANDSHAKE;
437                return 0;
438        }
439
440#if GNUTLS_VERSION_NUMBER >= 0x03000d && GNUTLS_VERSION_NUMBER <= 0x030012
441        if (ssl_errno == SSL_AGAIN) {
442                return 0;
443        }
444#endif
445
446        return gnutls_record_check_pending(((struct scd*) conn)->session) != 0;
447}
448
449void ssl_disconnect(void *conn_)
450{
451        struct scd *conn = conn_;
452
453        if (conn->inpa != -1) {
454                b_event_remove(conn->inpa);
455        }
456
457        if (conn->established) {
458                gnutls_bye(conn->session, GNUTLS_SHUT_WR);
459        }
460
461        closesocket(conn->fd);
462
463        if (conn->session) {
464                gnutls_deinit(conn->session);
465        }
466        g_free(conn->hostname);
467        g_free(conn);
468}
469
470int ssl_getfd(void *conn)
471{
472        return(((struct scd*) conn)->fd);
473}
474
475b_input_condition ssl_getdirection(void *conn)
476{
477        return(gnutls_record_get_direction(((struct scd*) conn)->session) ?
478               B_EV_IO_WRITE : B_EV_IO_READ);
479}
480
481size_t ssl_des3_encrypt(const unsigned char *key, size_t key_len, const unsigned char *input,
482                        size_t input_len, const unsigned char *iv, unsigned char **res)
483{
484        gcry_cipher_hd_t gcr;
485        gcry_error_t st;
486
487        ssl_init();
488
489        *res = g_malloc(input_len);
490        st = gcry_cipher_open(&gcr, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_CBC, 0) ||
491             gcry_cipher_setkey(gcr, key, key_len) ||
492             gcry_cipher_setiv(gcr, iv, 8) ||
493             gcry_cipher_encrypt(gcr, *res, input_len, input, input_len);
494
495        gcry_cipher_close(gcr);
496
497        if (st == 0) {
498                return input_len;
499        }
500
501        g_free(*res);
502        return 0;
503}
Note: See TracBrowser for help on using the repository browser.