source: lib/ssl_gnutls.c @ 59cd92b

Last change on this file since 59cd92b was 59cd92b, checked in by Wilmer van der Gaast <wilmer@…>, at 2011-12-29T20:30:43Z

Keep only one xcred object globally instead of one per connection. With
verification, this object gets pretty huge and there's no need to have it
more than once.

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