source: lib/ssl_gnutls.c @ 4c73ba62

Last change on this file since 4c73ba62 was 59c03bd, checked in by Wilmer van der Gaast <wilmer@…>, at 2012-01-03T23:53:28Z

A few more SSL fixes merged from AopicieR. This also fixes OpenSSL compile
issues (bug #881).

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