source: lib/ssl_gnutls.c @ 3f661849

Last change on this file since 3f661849 was 3f661849, checked in by Wilmer van der Gaast <wilmer@…>, at 2012-12-24T12:51:26Z

SNI client support in GnuTLS+OpenSSL modules.

  • Property mode set to 100644
File size: 11.3 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        if( conn->hostname && !isdigit( conn->hostname[0] ) )
276                gnutls_server_name_set( conn->session, GNUTLS_NAME_DNS,
277                                        conn->hostname, strlen( conn->hostname ) );
278       
279        sock_make_nonblocking( conn->fd );
280        gnutls_transport_set_ptr( conn->session, (gnutls_transport_ptr_t) GNUTLS_STUPID_CAST conn->fd );
281       
282        return ssl_handshake( data, source, cond );
283}
284
285static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond )
286{
287        struct scd *conn = data;
288        int st, stver;
289       
290        if( ( st = gnutls_handshake( conn->session ) ) < 0 )
291        {
292                if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
293                {
294                        conn->inpa = b_input_add( conn->fd, ssl_getdirection( conn ),
295                                                  ssl_handshake, data );
296                }
297                else
298                {
299                        conn->func( conn->data, 0, NULL, cond );
300                       
301                        gnutls_deinit( conn->session );
302                        closesocket( conn->fd );
303                       
304                        g_free( conn );
305                }
306        }
307        else
308        {
309                if( conn->verify && ( stver = verify_certificate_callback( conn->session ) ) != 0 )
310                {
311                        conn->func( conn->data, stver, NULL, cond );
312
313                        gnutls_deinit( conn->session );
314                        closesocket( conn->fd );
315
316                        g_free( conn );
317                }
318                else
319                {
320                        /* For now we can't handle non-blocking perfectly everywhere... */
321                        sock_make_blocking( conn->fd );
322               
323                        conn->established = TRUE;
324                        conn->func( conn->data, 0, conn, cond );
325                }
326        }
327       
328        return FALSE;
329}
330
331int ssl_read( void *conn, char *buf, int len )
332{
333        int st;
334       
335        if( !((struct scd*)conn)->established )
336        {
337                ssl_errno = SSL_NOHANDSHAKE;
338                return -1;
339        }
340       
341        st = gnutls_record_recv( ((struct scd*)conn)->session, buf, len );
342       
343        ssl_errno = SSL_OK;
344        if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
345                ssl_errno = SSL_AGAIN;
346       
347        if( SSLDEBUG && getenv( "BITLBEE_DEBUG" ) && st > 0 ) len = write( 2, buf, st );
348       
349        return st;
350}
351
352int ssl_write( void *conn, const char *buf, int len )
353{
354        int st;
355       
356        if( !((struct scd*)conn)->established )
357        {
358                ssl_errno = SSL_NOHANDSHAKE;
359                return -1;
360        }
361       
362        st = gnutls_record_send( ((struct scd*)conn)->session, buf, len );
363       
364        ssl_errno = SSL_OK;
365        if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
366                ssl_errno = SSL_AGAIN;
367       
368        if( SSLDEBUG && getenv( "BITLBEE_DEBUG" ) && st > 0 ) len = write( 2, buf, st );
369       
370        return st;
371}
372
373int ssl_pending( void *conn )
374{
375        if( conn == NULL )
376                return 0;
377       
378        if( !((struct scd*)conn)->established )
379        {
380                ssl_errno = SSL_NOHANDSHAKE;
381                return 0;
382        }
383
384#if GNUTLS_VERSION_NUMBER >= 0x03000d && GNUTLS_VERSION_NUMBER <= 0x030012
385        if( ssl_errno == SSL_AGAIN )
386                return 0;
387#endif
388       
389        return gnutls_record_check_pending( ((struct scd*)conn)->session ) != 0;
390}
391
392void ssl_disconnect( void *conn_ )
393{
394        struct scd *conn = conn_;
395       
396        if( conn->inpa != -1 )
397                b_event_remove( conn->inpa );
398       
399        if( conn->established )
400                gnutls_bye( conn->session, GNUTLS_SHUT_WR );
401       
402        closesocket( conn->fd );
403       
404        if( conn->session )
405                gnutls_deinit( conn->session );
406        g_free( conn->hostname );
407        g_free( conn );
408}
409
410int ssl_getfd( void *conn )
411{
412        return( ((struct scd*)conn)->fd );
413}
414
415b_input_condition ssl_getdirection( void *conn )
416{
417        return( gnutls_record_get_direction( ((struct scd*)conn)->session ) ?
418                B_EV_IO_WRITE : B_EV_IO_READ );
419}
420
421size_t ssl_des3_encrypt( const unsigned char *key, size_t key_len, const unsigned char *input,
422                         size_t input_len, const unsigned char *iv, unsigned char **res )
423{
424        gcry_cipher_hd_t gcr;
425        gcry_error_t st;
426       
427        ssl_init();
428       
429        *res = g_malloc( input_len  );
430        st = gcry_cipher_open( &gcr, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_CBC, 0 ) ||
431             gcry_cipher_setkey( gcr, key, key_len ) ||
432             gcry_cipher_setiv( gcr, iv, 8 ) ||
433             gcry_cipher_encrypt( gcr, *res, input_len, input, input_len );
434       
435        gcry_cipher_close( gcr );
436       
437        if( st == 0 )
438                return input_len;
439       
440        g_free( *res );
441        return 0;
442}
Note: See TracBrowser for help on using the repository browser.