source: lib/ssl_gnutls.c @ 2fb1262

Last change on this file since 2fb1262 was 2fb1262, checked in by Wilmer van der Gaast <wilmer@…>, at 2012-11-11T18:22:39Z

Tiny cleanup. Fixing some memory leaks (why did I not notice so far that
those free()s were commented out?).

  • Property mode set to 100644
File size: 11.1 KB
Line 
1  /********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2011 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., 59 Temple Place,
23  Suite 330, Boston, MA  02111-1307  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{
54        ssl_input_function func;
55        gpointer data;
56        int fd;
57        gboolean established;
58        int inpa;
59        char *hostname;
60        gboolean verify;
61       
62        gnutls_session_t session;
63};
64
65static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond );
66static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition cond );
67static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond );
68
69static void ssl_deinit( void );
70
71static void ssl_log( int level, const char *line )
72{
73        printf( "%d %s", level, line );
74}
75
76void ssl_init( void )
77{
78        if( initialized )
79                return;
80       
81        gnutls_global_init();
82        gnutls_certificate_allocate_credentials( &xcred );
83        if( global.conf->cafile )
84        {
85                gnutls_certificate_set_x509_trust_file( xcred, global.conf->cafile, GNUTLS_X509_FMT_PEM );
86               
87                /* Not needed in GnuTLS 2.11+ but we support older versions for now. */
88                gnutls_certificate_set_verify_flags( xcred, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT );
89        }
90        initialized = TRUE;
91       
92        gnutls_global_set_log_function( ssl_log );
93        /*
94        gnutls_global_set_log_level( 3 );
95        */
96       
97        atexit( ssl_deinit );
98}
99
100static void ssl_deinit( void )
101{
102        gnutls_global_deinit();
103        gnutls_certificate_free_credentials( xcred );
104}
105
106void *ssl_connect( char *host, int port, gboolean verify, ssl_input_function func, gpointer data )
107{
108        struct scd *conn = g_new0( struct scd, 1 );
109       
110        conn->fd = proxy_connect( host, port, ssl_connected, conn );
111        conn->func = func;
112        conn->data = data;
113        conn->inpa = -1;
114        conn->hostname = g_strdup( host );
115        conn->verify = verify && global.conf->cafile;
116       
117        if( conn->fd < 0 )
118        {
119                g_free( conn );
120                return NULL;
121        }
122       
123        return conn;
124}
125
126void *ssl_starttls( int fd, char *hostname, gboolean verify, ssl_input_function func, gpointer data )
127{
128        struct scd *conn = g_new0( struct scd, 1 );
129       
130        conn->fd = fd;
131        conn->func = func;
132        conn->data = data;
133        conn->inpa = -1;
134        conn->hostname = g_strdup( hostname );
135       
136        /* For now, SSL verification is globally enabled by setting the cafile
137           setting in bitlbee.conf. Commented out by default because probably
138           not everyone has this file in the same place and plenty of folks
139           may not have the cert of their private Jabber server in it. */
140        conn->verify = verify && global.conf->cafile;
141       
142        /* This function should be called via a (short) timeout instead of
143           directly from here, because these SSL calls are *supposed* to be
144           *completely* asynchronous and not ready yet when this function
145           (or *_connect, for examle) returns. Also, errors are reported via
146           the callback function, not via this function's return value.
147           
148           In short, doing things like this makes the rest of the code a lot
149           simpler. */
150       
151        b_timeout_add( 1, ssl_starttls_real, conn );
152       
153        return conn;
154}
155
156static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition cond )
157{
158        struct scd *conn = data;
159       
160        return ssl_connected( conn, conn->fd, B_EV_IO_WRITE );
161}
162
163static int verify_certificate_callback( gnutls_session_t session )
164{
165        unsigned int status;
166        const gnutls_datum_t *cert_list;
167        unsigned int cert_list_size;
168        int gnutlsret;
169        int verifyret = 0;
170        gnutls_x509_crt_t cert;
171        struct scd *conn;
172       
173        conn = gnutls_session_get_ptr( session );
174
175        gnutlsret = gnutls_certificate_verify_peers2( session, &status );
176        if( gnutlsret < 0 )
177                return VERIFY_CERT_ERROR;
178
179        if( status & GNUTLS_CERT_INVALID )
180                verifyret |= VERIFY_CERT_INVALID;
181
182        if( status & GNUTLS_CERT_REVOKED )
183                verifyret |= VERIFY_CERT_REVOKED;
184
185        if( status & GNUTLS_CERT_SIGNER_NOT_FOUND )
186                verifyret |= VERIFY_CERT_SIGNER_NOT_FOUND;
187
188        if( status & GNUTLS_CERT_SIGNER_NOT_CA )
189                verifyret |= VERIFY_CERT_SIGNER_NOT_CA;
190
191        if( status & GNUTLS_CERT_INSECURE_ALGORITHM )
192                verifyret |= VERIFY_CERT_INSECURE_ALGORITHM;
193
194#ifdef GNUTLS_CERT_NOT_ACTIVATED
195        /* Amusingly, the GnuTLS function used above didn't check for expiry
196           until GnuTLS 2.8 or so. (See CVE-2009-1417) */
197        if( status & GNUTLS_CERT_NOT_ACTIVATED )
198                verifyret |= VERIFY_CERT_NOT_ACTIVATED;
199
200        if( status & GNUTLS_CERT_EXPIRED )
201                verifyret |= VERIFY_CERT_EXPIRED;
202#endif
203
204        if( gnutls_certificate_type_get( session ) != GNUTLS_CRT_X509 || gnutls_x509_crt_init( &cert ) < 0 )
205                return VERIFY_CERT_ERROR;
206
207        cert_list = gnutls_certificate_get_peers( session, &cert_list_size );
208        if( cert_list == NULL || gnutls_x509_crt_import( cert, &cert_list[0], GNUTLS_X509_FMT_DER ) < 0 )
209                return VERIFY_CERT_ERROR;
210
211        if( !gnutls_x509_crt_check_hostname( cert, conn->hostname ) )
212        {
213                verifyret |= VERIFY_CERT_INVALID;
214                verifyret |= VERIFY_CERT_WRONG_HOSTNAME;
215        }
216
217        gnutls_x509_crt_deinit( cert );
218
219        return verifyret;
220}
221
222char *ssl_verify_strerror( int code )
223{
224        GString *ret = g_string_new( "" );
225       
226        if( code & VERIFY_CERT_REVOKED )
227                g_string_append( ret, "certificate has been revoked, " );
228        if( code & VERIFY_CERT_SIGNER_NOT_FOUND )
229                g_string_append( ret, "certificate hasn't got a known issuer, " );
230        if( code & VERIFY_CERT_SIGNER_NOT_CA )
231                g_string_append( ret, "certificate's issuer is not a CA, " );
232        if( code & VERIFY_CERT_INSECURE_ALGORITHM )
233                g_string_append( ret, "certificate uses an insecure algorithm, " );
234        if( code & VERIFY_CERT_NOT_ACTIVATED )
235                g_string_append( ret, "certificate has not been activated, " );
236        if( code & VERIFY_CERT_EXPIRED )
237                g_string_append( ret, "certificate has expired, " );
238        if( code & VERIFY_CERT_WRONG_HOSTNAME )
239                g_string_append( ret, "certificate hostname mismatch, " );
240       
241        if( ret->len == 0 )
242        {
243                g_string_free( ret, TRUE );
244                return NULL;
245        }
246        else
247        {
248                g_string_truncate( ret, ret->len - 2 );
249                return g_string_free( ret, FALSE );
250        }
251}
252
253static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond )
254{
255        struct scd *conn = data;
256       
257        if( source == -1 )
258        {
259                conn->func( conn->data, 0, NULL, cond );
260                g_free( conn );
261                return FALSE;
262        }
263       
264        ssl_init();
265       
266        gnutls_init( &conn->session, GNUTLS_CLIENT );
267        gnutls_session_set_ptr( conn->session, (void *) conn );
268#if GNUTLS_VERSION_NUMBER < 0x020c00
269        gnutls_transport_set_lowat( conn->session, 0 );
270#endif
271        gnutls_set_default_priority( conn->session );
272        gnutls_credentials_set( conn->session, GNUTLS_CRD_CERTIFICATE, xcred );
273       
274        sock_make_nonblocking( conn->fd );
275        gnutls_transport_set_ptr( conn->session, (gnutls_transport_ptr_t) GNUTLS_STUPID_CAST conn->fd );
276       
277        return ssl_handshake( data, source, cond );
278}
279
280static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond )
281{
282        struct scd *conn = data;
283        int st, stver;
284       
285        if( ( st = gnutls_handshake( conn->session ) ) < 0 )
286        {
287                if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
288                {
289                        conn->inpa = b_input_add( conn->fd, ssl_getdirection( conn ),
290                                                  ssl_handshake, data );
291                }
292                else
293                {
294                        conn->func( conn->data, 0, NULL, cond );
295                       
296                        gnutls_deinit( conn->session );
297                        closesocket( conn->fd );
298                       
299                        g_free( conn );
300                }
301        }
302        else
303        {
304                if( conn->verify && ( stver = verify_certificate_callback( conn->session ) ) != 0 )
305                {
306                        conn->func( conn->data, stver, NULL, cond );
307
308                        gnutls_deinit( conn->session );
309                        closesocket( conn->fd );
310
311                        g_free( conn );
312                }
313                else
314                {
315                        /* For now we can't handle non-blocking perfectly everywhere... */
316                        sock_make_blocking( conn->fd );
317               
318                        conn->established = TRUE;
319                        conn->func( conn->data, 0, conn, cond );
320                }
321        }
322       
323        return FALSE;
324}
325
326int ssl_read( void *conn, char *buf, int len )
327{
328        int st;
329       
330        if( !((struct scd*)conn)->established )
331        {
332                ssl_errno = SSL_NOHANDSHAKE;
333                return -1;
334        }
335       
336        st = gnutls_record_recv( ((struct scd*)conn)->session, buf, len );
337       
338        ssl_errno = SSL_OK;
339        if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
340                ssl_errno = SSL_AGAIN;
341       
342        if( SSLDEBUG && getenv( "BITLBEE_DEBUG" ) && st > 0 ) len = write( 2, buf, st );
343       
344        return st;
345}
346
347int ssl_write( void *conn, const char *buf, int len )
348{
349        int st;
350       
351        if( !((struct scd*)conn)->established )
352        {
353                ssl_errno = SSL_NOHANDSHAKE;
354                return -1;
355        }
356       
357        st = gnutls_record_send( ((struct scd*)conn)->session, buf, len );
358       
359        ssl_errno = SSL_OK;
360        if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
361                ssl_errno = SSL_AGAIN;
362       
363        if( SSLDEBUG && getenv( "BITLBEE_DEBUG" ) && st > 0 ) len = write( 2, buf, st );
364       
365        return st;
366}
367
368int ssl_pending( void *conn )
369{
370        if( conn == NULL )
371                return 0;
372       
373        if( !((struct scd*)conn)->established )
374        {
375                ssl_errno = SSL_NOHANDSHAKE;
376                return 0;
377        }
378
379#if GNUTLS_VERSION_NUMBER >= 0x03000d && GNUTLS_VERSION_NUMBER <= 0x030012
380        if( ssl_errno == SSL_AGAIN )
381                return 0;
382#endif
383       
384        return gnutls_record_check_pending( ((struct scd*)conn)->session ) != 0;
385}
386
387void ssl_disconnect( void *conn_ )
388{
389        struct scd *conn = conn_;
390       
391        if( conn->inpa != -1 )
392                b_event_remove( conn->inpa );
393       
394        if( conn->established )
395                gnutls_bye( conn->session, GNUTLS_SHUT_WR );
396       
397        closesocket( conn->fd );
398       
399        if( conn->session )
400                gnutls_deinit( conn->session );
401        g_free( conn->hostname );
402        g_free( conn );
403}
404
405int ssl_getfd( void *conn )
406{
407        return( ((struct scd*)conn)->fd );
408}
409
410b_input_condition ssl_getdirection( void *conn )
411{
412        return( gnutls_record_get_direction( ((struct scd*)conn)->session ) ?
413                B_EV_IO_WRITE : B_EV_IO_READ );
414}
415
416size_t ssl_des3_encrypt( const unsigned char *key, size_t key_len, const unsigned char *input,
417                         size_t input_len, const unsigned char *iv, unsigned char **res )
418{
419        gcry_cipher_hd_t gcr;
420        gcry_error_t st;
421       
422        ssl_init();
423       
424        *res = g_malloc( input_len  );
425        st = gcry_cipher_open( &gcr, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_CBC, 0 ) ||
426             gcry_cipher_setkey( gcr, key, key_len ) ||
427             gcry_cipher_setiv( gcr, iv, 8 ) ||
428             gcry_cipher_encrypt( gcr, *res, input_len, input, input_len );
429       
430        gcry_cipher_close( gcr );
431       
432        if( st == 0 )
433                return input_len;
434       
435        g_free( *res );
436        return 0;
437}
Note: See TracBrowser for help on using the repository browser.