source: lib/ssl_gnutls.c @ 25b05b7

Last change on this file since 25b05b7 was 78b8401, checked in by Wilmer van der Gaast <wilmer@…>, at 2011-12-19T17:22:37Z

Move conversion of status codes to status messages into SSL libs.

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