source: lib/ssl_gnutls.c @ 536dfa1

Last change on this file since 536dfa1 was 8f976e6, checked in by Wilmer van der Gaast <wilmer@…>, at 2012-10-30T23:41:43Z

SSL fixes from Michal Suchanek.

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