source: lib/ssl_gnutls.c @ 3558fea

Last change on this file since 3558fea was 5513f3e, checked in by Wilmer van der Gaast <wilmer@…>, at 2011-12-24T14:52:35Z

Fix compatibility with old GnuTLS versions, but with a warning. See
http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-1417 for details.

  • 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-2004 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;
40
41#include <limits.h>
42
43#if defined(ULONG_MAX) && ULONG_MAX > 4294967295UL
44#define GNUTLS_STUPID_CAST (long)
45#else
46#define GNUTLS_STUPID_CAST (int)
47#endif
48
49#define SSLDEBUG 0
50
51struct scd
52{
53        ssl_input_function func;
54        gpointer data;
55        int fd;
56        gboolean established;
57        int inpa;
58        char *hostname;
59        gboolean verify;
60       
61        gnutls_session session;
62        gnutls_certificate_credentials xcred;
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
69
70void ssl_init( void )
71{
72        if( initialized )
73                return;
74       
75        gnutls_global_init();
76        initialized = TRUE;
77        atexit( gnutls_global_deinit );
78}
79
80void *ssl_connect( char *host, int port, gboolean verify, ssl_input_function func, gpointer data )
81{
82        struct scd *conn = g_new0( struct scd, 1 );
83       
84        conn->fd = proxy_connect( host, port, ssl_connected, conn );
85        conn->func = func;
86        conn->data = data;
87        conn->inpa = -1;
88        conn->hostname = g_strdup( host );
89        conn->verify = verify && global.conf->cafile;
90       
91        if( conn->fd < 0 )
92        {
93                g_free( conn );
94                return NULL;
95        }
96       
97        return conn;
98}
99
100void *ssl_starttls( int fd, char *hostname, gboolean verify, ssl_input_function func, gpointer data )
101{
102        struct scd *conn = g_new0( struct scd, 1 );
103       
104        conn->fd = fd;
105        conn->func = func;
106        conn->data = data;
107        conn->inpa = -1;
108        conn->hostname = hostname;
109       
110        /* For now, SSL verification is globally enabled by setting the cafile
111           setting in bitlbee.conf. Commented out by default because probably
112           not everyone has this file in the same place and plenty of folks
113           may not have the cert of their private Jabber server in it. */
114        conn->verify = verify && global.conf->cafile;
115       
116        /* This function should be called via a (short) timeout instead of
117           directly from here, because these SSL calls are *supposed* to be
118           *completely* asynchronous and not ready yet when this function
119           (or *_connect, for examle) returns. Also, errors are reported via
120           the callback function, not via this function's return value.
121           
122           In short, doing things like this makes the rest of the code a lot
123           simpler. */
124       
125        b_timeout_add( 1, ssl_starttls_real, conn );
126       
127        return conn;
128}
129
130static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition cond )
131{
132        struct scd *conn = data;
133       
134        return ssl_connected( conn, conn->fd, B_EV_IO_WRITE );
135}
136
137static int verify_certificate_callback( gnutls_session_t session )
138{
139        unsigned int status;
140        const gnutls_datum_t *cert_list;
141        unsigned int cert_list_size;
142        int gnutlsret;
143        int verifyret = 0;
144        gnutls_x509_crt_t cert;
145        const char *hostname;
146       
147        hostname = gnutls_session_get_ptr(session );
148
149        gnutlsret = gnutls_certificate_verify_peers2( session, &status );
150        if( gnutlsret < 0 )
151                return VERIFY_CERT_ERROR;
152
153        if( status & GNUTLS_CERT_INVALID )
154                verifyret |= VERIFY_CERT_INVALID;
155
156        if( status & GNUTLS_CERT_REVOKED )
157                verifyret |= VERIFY_CERT_REVOKED;
158
159        if( status & GNUTLS_CERT_SIGNER_NOT_FOUND )
160                verifyret |= VERIFY_CERT_SIGNER_NOT_FOUND;
161
162        if( status & GNUTLS_CERT_SIGNER_NOT_CA )
163                verifyret |= VERIFY_CERT_SIGNER_NOT_CA;
164
165        if( status & GNUTLS_CERT_INSECURE_ALGORITHM )
166                verifyret |= VERIFY_CERT_INSECURE_ALGORITHM;
167
168#ifdef GNUTLS_CERT_NOT_ACTIVATED
169        /* Amusingly, the GnuTLS function used above didn't check for expiry
170           until GnuTLS 2.8 or so. (See CVE-2009-1417) */
171        if( status & GNUTLS_CERT_NOT_ACTIVATED )
172                verifyret |= VERIFY_CERT_NOT_ACTIVATED;
173
174        if( status & GNUTLS_CERT_EXPIRED )
175                verifyret |= VERIFY_CERT_EXPIRED;
176#endif
177
178        /* The following check is already performed inside
179         * gnutls_certificate_verify_peers2, so we don't need it.
180
181         * if( gnutls_certificate_type_get( session ) != GNUTLS_CRT_X509 )
182         * return GNUTLS_E_CERTIFICATE_ERROR;
183         */
184
185        if( gnutls_x509_crt_init( &cert ) < 0 )
186                return VERIFY_CERT_ERROR;
187
188        cert_list = gnutls_certificate_get_peers( session, &cert_list_size );
189        if( cert_list == NULL || gnutls_x509_crt_import( cert, &cert_list[0], GNUTLS_X509_FMT_DER ) < 0 )
190                return VERIFY_CERT_ERROR;
191
192        if( !gnutls_x509_crt_check_hostname( cert, hostname ) )
193        {
194                verifyret |= VERIFY_CERT_INVALID;
195                verifyret |= VERIFY_CERT_WRONG_HOSTNAME;
196        }
197
198        gnutls_x509_crt_deinit( cert );
199
200        return verifyret;
201}
202
203char *ssl_verify_strerror( int code )
204{
205        GString *ret = g_string_new( "" );
206       
207        if( code & VERIFY_CERT_REVOKED )
208                g_string_append( ret, "certificate has been revoked, " );
209        if( code & VERIFY_CERT_SIGNER_NOT_FOUND )
210                g_string_append( ret, "certificate hasn't got a known issuer, " );
211        if( code & VERIFY_CERT_SIGNER_NOT_CA )
212                g_string_append( ret, "certificate's issuer is not a CA, " );
213        if( code & VERIFY_CERT_INSECURE_ALGORITHM )
214                g_string_append( ret, "certificate uses an insecure algorithm, " );
215        if( code & VERIFY_CERT_NOT_ACTIVATED )
216                g_string_append( ret, "certificate has not been activated, " );
217        if( code & VERIFY_CERT_EXPIRED )
218                g_string_append( ret, "certificate has expired, " );
219        if( code & VERIFY_CERT_WRONG_HOSTNAME )
220                g_string_append( ret, "certificate hostname mismatch, " );
221       
222        if( ret->len == 0 )
223        {
224                g_string_free( ret, TRUE );
225                return NULL;
226        }
227        else
228        {
229                g_string_truncate( ret, ret->len - 2 );
230                return g_string_free( ret, FALSE );
231        }
232}
233
234static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond )
235{
236        struct scd *conn = data;
237       
238        if( source == -1 )
239        {
240                conn->func( conn->data, 0, NULL, cond );
241                g_free( conn );
242                return FALSE;
243        }
244       
245        ssl_init();
246       
247        gnutls_certificate_allocate_credentials( &conn->xcred );
248        if( conn->verify && global.conf->cafile )
249        {
250                gnutls_certificate_set_x509_trust_file( conn->xcred, global.conf->cafile, GNUTLS_X509_FMT_PEM );
251                gnutls_certificate_set_verify_flags( conn->xcred, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT );
252        }
253
254        gnutls_init( &conn->session, GNUTLS_CLIENT );
255        if( conn->verify )
256                gnutls_session_set_ptr( conn->session, (void *) conn->hostname );
257#if GNUTLS_VERSION_NUMBER < 0x020c00
258        gnutls_transport_set_lowat( conn->session, 0 );
259#endif
260        gnutls_set_default_priority( conn->session );
261        gnutls_credentials_set( conn->session, GNUTLS_CRD_CERTIFICATE, conn->xcred );
262       
263        sock_make_nonblocking( conn->fd );
264        gnutls_transport_set_ptr( conn->session, (gnutls_transport_ptr) GNUTLS_STUPID_CAST conn->fd );
265       
266        return ssl_handshake( data, source, cond );
267}
268
269static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond )
270{
271        struct scd *conn = data;
272        int st, stver;
273       
274        if( ( st = gnutls_handshake( conn->session ) ) < 0 )
275        {
276                if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
277                {
278                        conn->inpa = b_input_add( conn->fd, ssl_getdirection( conn ),
279                                                  ssl_handshake, data );
280                }
281                else
282                {
283                        conn->func( conn->data, 0, NULL, cond );
284                       
285                        gnutls_deinit( conn->session );
286                        gnutls_certificate_free_credentials( conn->xcred );
287                        closesocket( conn->fd );
288                       
289                        g_free( conn );
290                }
291        }
292        else
293        {
294                if( conn->verify && ( stver = verify_certificate_callback( conn->session ) ) != 0 )
295                {
296                        conn->func( conn->data, stver, NULL, cond );
297
298                        gnutls_deinit( conn->session );
299                        gnutls_certificate_free_credentials( conn->xcred );
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        if( conn->xcred )
388                gnutls_certificate_free_credentials( conn->xcred );
389        g_free( conn );
390}
391
392int ssl_getfd( void *conn )
393{
394        return( ((struct scd*)conn)->fd );
395}
396
397b_input_condition ssl_getdirection( void *conn )
398{
399        return( gnutls_record_get_direction( ((struct scd*)conn)->session ) ?
400                B_EV_IO_WRITE : B_EV_IO_READ );
401}
402
403size_t ssl_des3_encrypt( const unsigned char *key, size_t key_len, const unsigned char *input,
404                         size_t input_len, const unsigned char *iv, unsigned char **res )
405{
406        gcry_cipher_hd_t gcr;
407        gcry_error_t st;
408       
409        ssl_init();
410       
411        *res = g_malloc( input_len  );
412        st = gcry_cipher_open( &gcr, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_CBC, 0 ) ||
413             gcry_cipher_setkey( gcr, key, key_len ) ||
414             gcry_cipher_setiv( gcr, iv, 8 ) ||
415             gcry_cipher_encrypt( gcr, *res, input_len, input, input_len );
416       
417        gcry_cipher_close( gcr );
418       
419        if( st == 0 )
420                return input_len;
421       
422        g_free( *res );
423        return 0;
424}
Note: See TracBrowser for help on using the repository browser.