source: lib/ssl_gnutls.c @ cc6fdf8

Last change on this file since cc6fdf8 was b006464, checked in by Wilmer van der Gaast <wilmer@…>, at 2012-11-17T23:51:21Z

Merge mainline.

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