source: lib/ssl_gnutls.c @ e9caacd

Last change on this file since e9caacd was 632f3d4, checked in by Wilmer van der Gaast <wilmer@…>, at 2012-04-10T09:14:58Z

Work-around for what turned out to be a GnuTLS bug (#938). From 3.0.13
until 3.0.18 gnutls_record_check_pending() returns non-0 even if the data
read so far is an incomplete record and can not yet be read. This can get
BitlBee's http_client stuck in a semi-infinite loop.

  • 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 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 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 = 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        const char *hostname;
172       
173        hostname = 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, 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        if( conn->verify )
268                gnutls_session_set_ptr( conn->session, (void *) conn->hostname );
269#if GNUTLS_VERSION_NUMBER < 0x020c00
270        gnutls_transport_set_lowat( conn->session, 0 );
271#endif
272        gnutls_set_default_priority( conn->session );
273        gnutls_credentials_set( conn->session, GNUTLS_CRD_CERTIFICATE, xcred );
274       
275        sock_make_nonblocking( conn->fd );
276        gnutls_transport_set_ptr( conn->session, (gnutls_transport_ptr) GNUTLS_STUPID_CAST conn->fd );
277       
278        return ssl_handshake( data, source, cond );
279}
280
281static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond )
282{
283        struct scd *conn = data;
284        int st, stver;
285       
286        if( ( st = gnutls_handshake( conn->session ) ) < 0 )
287        {
288                if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
289                {
290                        conn->inpa = b_input_add( conn->fd, ssl_getdirection( conn ),
291                                                  ssl_handshake, data );
292                }
293                else
294                {
295                        conn->func( conn->data, 0, NULL, cond );
296                       
297                        gnutls_deinit( conn->session );
298                        closesocket( conn->fd );
299                       
300                        g_free( conn );
301                }
302        }
303        else
304        {
305                if( conn->verify && ( stver = verify_certificate_callback( conn->session ) ) != 0 )
306                {
307                        conn->func( conn->data, stver, NULL, cond );
308
309                        gnutls_deinit( conn->session );
310                        closesocket( conn->fd );
311
312                        g_free( conn );
313                }
314                else
315                {
316                        /* For now we can't handle non-blocking perfectly everywhere... */
317                        sock_make_blocking( conn->fd );
318               
319                        conn->established = TRUE;
320                        conn->func( conn->data, 0, conn, cond );
321                }
322        }
323       
324        return FALSE;
325}
326
327int ssl_read( void *conn, char *buf, int len )
328{
329        int st;
330       
331        if( !((struct scd*)conn)->established )
332        {
333                ssl_errno = SSL_NOHANDSHAKE;
334                return -1;
335        }
336       
337        st = gnutls_record_recv( ((struct scd*)conn)->session, buf, len );
338       
339        ssl_errno = SSL_OK;
340        if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
341                ssl_errno = SSL_AGAIN;
342       
343        if( SSLDEBUG && getenv( "BITLBEE_DEBUG" ) && st > 0 ) len = write( 2, buf, st );
344       
345        return st;
346}
347
348int ssl_write( void *conn, const char *buf, int len )
349{
350        int st;
351       
352        if( !((struct scd*)conn)->established )
353        {
354                ssl_errno = SSL_NOHANDSHAKE;
355                return -1;
356        }
357       
358        st = gnutls_record_send( ((struct scd*)conn)->session, buf, len );
359       
360        ssl_errno = SSL_OK;
361        if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
362                ssl_errno = SSL_AGAIN;
363       
364        if( SSLDEBUG && getenv( "BITLBEE_DEBUG" ) && st > 0 ) len = write( 2, buf, st );
365       
366        return st;
367}
368
369int ssl_pending( void *conn )
370{
371        if( conn == NULL )
372                return 0;
373       
374        if( !((struct scd*)conn)->established )
375        {
376                ssl_errno = SSL_NOHANDSHAKE;
377                return 0;
378        }
379
380#if GNUTLS_VERSION_NUMBER >= 0x03000d && GNUTLS_VERSION_NUMBER <= 0x030012
381        if( ssl_errno == SSL_AGAIN )
382                return 0;
383#endif
384       
385        return gnutls_record_check_pending( ((struct scd*)conn)->session ) != 0;
386}
387
388void ssl_disconnect( void *conn_ )
389{
390        struct scd *conn = conn_;
391       
392        if( conn->inpa != -1 )
393                b_event_remove( conn->inpa );
394       
395        if( conn->established )
396                gnutls_bye( conn->session, GNUTLS_SHUT_WR );
397       
398        closesocket( conn->fd );
399       
400        if( conn->session )
401                gnutls_deinit( conn->session );
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.