source: lib/ssl_gnutls.c @ 9f958f7

Last change on this file since 9f958f7 was a72dc2b, checked in by Wilmer van der Gaast <wilmer@…>, at 2011-12-19T17:57:20Z

Add verify argument to ssl_connect() so HTTPS-based stuff is also secure.
(Think of Twitter, but also MSN/Yahoo! authentication.)

  • 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-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        if( status & GNUTLS_CERT_NOT_ACTIVATED )
169                verifyret |= VERIFY_CERT_NOT_ACTIVATED;
170
171        if( status & GNUTLS_CERT_EXPIRED )
172                verifyret |= VERIFY_CERT_EXPIRED;
173
174        /* The following check is already performed inside
175         * gnutls_certificate_verify_peers2, so we don't need it.
176
177         * if( gnutls_certificate_type_get( session ) != GNUTLS_CRT_X509 )
178         * return GNUTLS_E_CERTIFICATE_ERROR;
179         */
180
181        if( gnutls_x509_crt_init( &cert ) < 0 )
182                return VERIFY_CERT_ERROR;
183
184        cert_list = gnutls_certificate_get_peers( session, &cert_list_size );
185        if( cert_list == NULL || gnutls_x509_crt_import( cert, &cert_list[0], GNUTLS_X509_FMT_DER ) < 0 )
186                return VERIFY_CERT_ERROR;
187
188        if( !gnutls_x509_crt_check_hostname( cert, hostname ) )
189        {
190                verifyret |= VERIFY_CERT_INVALID;
191                verifyret |= VERIFY_CERT_WRONG_HOSTNAME;
192        }
193
194        gnutls_x509_crt_deinit( cert );
195
196        return verifyret;
197}
198
199char *ssl_verify_strerror( int code )
200{
201        GString *ret = g_string_new( "" );
202       
203        if( code & VERIFY_CERT_REVOKED )
204                g_string_append( ret, "certificate has been revoked, " );
205        if( code & VERIFY_CERT_SIGNER_NOT_FOUND )
206                g_string_append( ret, "certificate hasn't got a known issuer, " );
207        if( code & VERIFY_CERT_SIGNER_NOT_CA )
208                g_string_append( ret, "certificate's issuer is not a CA, " );
209        if( code & VERIFY_CERT_INSECURE_ALGORITHM )
210                g_string_append( ret, "certificate uses an insecure algorithm, " );
211        if( code & VERIFY_CERT_NOT_ACTIVATED )
212                g_string_append( ret, "certificate has not been activated, " );
213        if( code & VERIFY_CERT_EXPIRED )
214                g_string_append( ret, "certificate has expired, " );
215        if( code & VERIFY_CERT_WRONG_HOSTNAME )
216                g_string_append( ret, "certificate hostname mismatch, " );
217       
218        if( ret->len == 0 )
219        {
220                g_string_free( ret, TRUE );
221                return NULL;
222        }
223        else
224        {
225                g_string_truncate( ret, ret->len - 2 );
226                return g_string_free( ret, FALSE );
227        }
228}
229
230static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond )
231{
232        struct scd *conn = data;
233       
234        if( source == -1 )
235        {
236                conn->func( conn->data, 0, NULL, cond );
237                g_free( conn );
238                return FALSE;
239        }
240       
241        ssl_init();
242       
243        gnutls_certificate_allocate_credentials( &conn->xcred );
244        if( conn->verify && global.conf->cafile )
245        {
246                gnutls_certificate_set_x509_trust_file( conn->xcred, global.conf->cafile, GNUTLS_X509_FMT_PEM );
247                gnutls_certificate_set_verify_flags( conn->xcred, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT );
248        }
249
250        gnutls_init( &conn->session, GNUTLS_CLIENT );
251        if( conn->verify )
252                gnutls_session_set_ptr( conn->session, (void *) conn->hostname );
253#if GNUTLS_VERSION_NUMBER < 0x020c00
254        gnutls_transport_set_lowat( conn->session, 0 );
255#endif
256        gnutls_set_default_priority( conn->session );
257        gnutls_credentials_set( conn->session, GNUTLS_CRD_CERTIFICATE, conn->xcred );
258       
259        sock_make_nonblocking( conn->fd );
260        gnutls_transport_set_ptr( conn->session, (gnutls_transport_ptr) GNUTLS_STUPID_CAST conn->fd );
261       
262        return ssl_handshake( data, source, cond );
263}
264
265static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond )
266{
267        struct scd *conn = data;
268        int st, stver;
269       
270        if( ( st = gnutls_handshake( conn->session ) ) < 0 )
271        {
272                if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
273                {
274                        conn->inpa = b_input_add( conn->fd, ssl_getdirection( conn ),
275                                                  ssl_handshake, data );
276                }
277                else
278                {
279                        conn->func( conn->data, 0, NULL, cond );
280                       
281                        gnutls_deinit( conn->session );
282                        gnutls_certificate_free_credentials( conn->xcred );
283                        closesocket( conn->fd );
284                       
285                        g_free( conn );
286                }
287        }
288        else
289        {
290                if( conn->verify && ( stver = verify_certificate_callback( conn->session ) ) != 0 )
291                {
292                        conn->func( conn->data, stver, NULL, cond );
293
294                        gnutls_deinit( conn->session );
295                        gnutls_certificate_free_credentials( conn->xcred );
296                        closesocket( conn->fd );
297
298                        g_free( conn );
299                }
300                else
301                {
302                        /* For now we can't handle non-blocking perfectly everywhere... */
303                        sock_make_blocking( conn->fd );
304               
305                        conn->established = TRUE;
306                        conn->func( conn->data, 0, conn, cond );
307                }
308        }
309       
310        return FALSE;
311}
312
313int ssl_read( void *conn, char *buf, int len )
314{
315        int st;
316       
317        if( !((struct scd*)conn)->established )
318        {
319                ssl_errno = SSL_NOHANDSHAKE;
320                return -1;
321        }
322       
323        st = gnutls_record_recv( ((struct scd*)conn)->session, buf, len );
324       
325        ssl_errno = SSL_OK;
326        if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
327                ssl_errno = SSL_AGAIN;
328       
329        if( SSLDEBUG && getenv( "BITLBEE_DEBUG" ) && st > 0 ) len = write( 2, buf, st );
330       
331        return st;
332}
333
334int ssl_write( void *conn, const char *buf, int len )
335{
336        int st;
337       
338        if( !((struct scd*)conn)->established )
339        {
340                ssl_errno = SSL_NOHANDSHAKE;
341                return -1;
342        }
343       
344        st = gnutls_record_send( ((struct scd*)conn)->session, buf, len );
345       
346        ssl_errno = SSL_OK;
347        if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
348                ssl_errno = SSL_AGAIN;
349       
350        if( SSLDEBUG && getenv( "BITLBEE_DEBUG" ) && st > 0 ) len = write( 2, buf, st );
351       
352        return st;
353}
354
355int ssl_pending( void *conn )
356{
357        if( conn == NULL )
358                return 0;
359       
360        if( !((struct scd*)conn)->established )
361        {
362                ssl_errno = SSL_NOHANDSHAKE;
363                return 0;
364        }
365       
366        return gnutls_record_check_pending( ((struct scd*)conn)->session ) != 0;
367}
368
369void ssl_disconnect( void *conn_ )
370{
371        struct scd *conn = conn_;
372       
373        if( conn->inpa != -1 )
374                b_event_remove( conn->inpa );
375       
376        if( conn->established )
377                gnutls_bye( conn->session, GNUTLS_SHUT_WR );
378       
379        closesocket( conn->fd );
380       
381        if( conn->session )
382                gnutls_deinit( conn->session );
383        if( conn->xcred )
384                gnutls_certificate_free_credentials( conn->xcred );
385        g_free( conn );
386}
387
388int ssl_getfd( void *conn )
389{
390        return( ((struct scd*)conn)->fd );
391}
392
393b_input_condition ssl_getdirection( void *conn )
394{
395        return( gnutls_record_get_direction( ((struct scd*)conn)->session ) ?
396                B_EV_IO_WRITE : B_EV_IO_READ );
397}
398
399size_t ssl_des3_encrypt( const unsigned char *key, size_t key_len, const unsigned char *input,
400                         size_t input_len, const unsigned char *iv, unsigned char **res )
401{
402        gcry_cipher_hd_t gcr;
403        gcry_error_t st;
404       
405        ssl_init();
406       
407        *res = g_malloc( input_len  );
408        st = gcry_cipher_open( &gcr, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_CBC, 0 ) ||
409             gcry_cipher_setkey( gcr, key, key_len ) ||
410             gcry_cipher_setiv( gcr, iv, 8 ) ||
411             gcry_cipher_encrypt( gcr, *res, input_len, input, input_len );
412       
413        gcry_cipher_close( gcr );
414       
415        if( st == 0 )
416                return input_len;
417       
418        g_free( *res );
419        return 0;
420}
Note: See TracBrowser for help on using the repository browser.