source: lib/ssl_gnutls.c @ 486ddb5

Last change on this file since 486ddb5 was 486ddb5, checked in by Wilmer van der Gaast <wilmer@…>, at 2011-12-19T14:50:58Z

Initial merge of tls_verify patch from AopicieR.

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