diff options
Diffstat (limited to 'lib/ssl_gnutls.c')
| -rw-r--r-- | lib/ssl_gnutls.c | 145 | 
1 files changed, 136 insertions, 9 deletions
| diff --git a/lib/ssl_gnutls.c b/lib/ssl_gnutls.c index ccab8aca..b4bc72d5 100644 --- a/lib/ssl_gnutls.c +++ b/lib/ssl_gnutls.c @@ -24,6 +24,7 @@  */  #include <gnutls/gnutls.h> +#include <gnutls/x509.h>  #include <gcrypt.h>  #include <fcntl.h>  #include <unistd.h> @@ -31,6 +32,7 @@  #include "ssl_client.h"  #include "sock.h"  #include "stdlib.h" +#include "bitlbee.h"  int ssl_errno = 0; @@ -53,6 +55,8 @@ struct scd  	int fd;  	gboolean established;  	int inpa; +	char *hostname; +	gboolean verify;  	gnutls_session session;  	gnutls_certificate_credentials xcred; @@ -73,7 +77,7 @@ void ssl_init( void )  	atexit( gnutls_global_deinit );  } -void *ssl_connect( char *host, int port, ssl_input_function func, gpointer data ) +void *ssl_connect( char *host, int port, gboolean verify, ssl_input_function func, gpointer data )  {  	struct scd *conn = g_new0( struct scd, 1 ); @@ -81,6 +85,8 @@ void *ssl_connect( char *host, int port, ssl_input_function func, gpointer data  	conn->func = func;  	conn->data = data;  	conn->inpa = -1; +	conn->hostname = g_strdup( host ); +	conn->verify = verify && global.conf->cafile;  	if( conn->fd < 0 )  	{ @@ -91,7 +97,7 @@ void *ssl_connect( char *host, int port, ssl_input_function func, gpointer data  	return conn;  } -void *ssl_starttls( int fd, ssl_input_function func, gpointer data ) +void *ssl_starttls( int fd, char *hostname, gboolean verify, ssl_input_function func, gpointer data )  {  	struct scd *conn = g_new0( struct scd, 1 ); @@ -99,6 +105,13 @@ void *ssl_starttls( int fd, ssl_input_function func, gpointer data )  	conn->func = func;  	conn->data = data;  	conn->inpa = -1; +	conn->hostname = hostname; +	 +	/* For now, SSL verification is globally enabled by setting the cafile +	   setting in bitlbee.conf. Commented out by default because probably +	   not everyone has this file in the same place and plenty of folks +	   may not have the cert of their private Jabber server in it. */ +	conn->verify = verify && global.conf->cafile;  	/* This function should be called via a (short) timeout instead of  	   directly from here, because these SSL calls are *supposed* to be @@ -121,13 +134,106 @@ static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition  	return ssl_connected( conn, conn->fd, B_EV_IO_WRITE );  } +static int verify_certificate_callback( gnutls_session_t session ) +{ +	unsigned int status; +	const gnutls_datum_t *cert_list; +	unsigned int cert_list_size; +	int gnutlsret; +	int verifyret = 0; +	gnutls_x509_crt_t cert; +	const char *hostname; +	 +	hostname = gnutls_session_get_ptr(session ); + +	gnutlsret = gnutls_certificate_verify_peers2( session, &status ); +	if( gnutlsret < 0 ) +		return VERIFY_CERT_ERROR; + +	if( status & GNUTLS_CERT_INVALID ) +		verifyret |= VERIFY_CERT_INVALID; + +	if( status & GNUTLS_CERT_REVOKED ) +		verifyret |= VERIFY_CERT_REVOKED; + +	if( status & GNUTLS_CERT_SIGNER_NOT_FOUND ) +		verifyret |= VERIFY_CERT_SIGNER_NOT_FOUND; + +	if( status & GNUTLS_CERT_SIGNER_NOT_CA ) +		verifyret |= VERIFY_CERT_SIGNER_NOT_CA; + +	if( status & GNUTLS_CERT_INSECURE_ALGORITHM ) +		verifyret |= VERIFY_CERT_INSECURE_ALGORITHM; + +	if( status & GNUTLS_CERT_NOT_ACTIVATED ) +		verifyret |= VERIFY_CERT_NOT_ACTIVATED; + +	if( status & GNUTLS_CERT_EXPIRED ) +		verifyret |= VERIFY_CERT_EXPIRED; + +	/* The following check is already performed inside  +	 * gnutls_certificate_verify_peers2, so we don't need it. + +	 * if( gnutls_certificate_type_get( session ) != GNUTLS_CRT_X509 ) +	 * return GNUTLS_E_CERTIFICATE_ERROR; +	 */ + +	if( gnutls_x509_crt_init( &cert ) < 0 ) +		return VERIFY_CERT_ERROR; + +	cert_list = gnutls_certificate_get_peers( session, &cert_list_size ); +	if( cert_list == NULL || gnutls_x509_crt_import( cert, &cert_list[0], GNUTLS_X509_FMT_DER ) < 0 ) +		return VERIFY_CERT_ERROR; + +	if( !gnutls_x509_crt_check_hostname( cert, hostname ) ) +	{ +		verifyret |= VERIFY_CERT_INVALID; +		verifyret |= VERIFY_CERT_WRONG_HOSTNAME; +	} + +	gnutls_x509_crt_deinit( cert ); + +	return verifyret; +} + +char *ssl_verify_strerror( int code ) +{ +	GString *ret = g_string_new( "" ); +	 +	if( code & VERIFY_CERT_REVOKED ) +		g_string_append( ret, "certificate has been revoked, " ); +	if( code & VERIFY_CERT_SIGNER_NOT_FOUND ) +		g_string_append( ret, "certificate hasn't got a known issuer, " ); +	if( code & VERIFY_CERT_SIGNER_NOT_CA ) +		g_string_append( ret, "certificate's issuer is not a CA, " ); +	if( code & VERIFY_CERT_INSECURE_ALGORITHM ) +		g_string_append( ret, "certificate uses an insecure algorithm, " ); +	if( code & VERIFY_CERT_NOT_ACTIVATED ) +		g_string_append( ret, "certificate has not been activated, " ); +	if( code & VERIFY_CERT_EXPIRED ) +		g_string_append( ret, "certificate has expired, " ); +	if( code & VERIFY_CERT_WRONG_HOSTNAME ) +		g_string_append( ret, "certificate hostname mismatch, " ); +	 +	if( ret->len == 0 ) +	{ +		g_string_free( ret, TRUE ); +		return NULL; +	} +	else +	{ +		g_string_truncate( ret, ret->len - 2 ); +		return g_string_free( ret, FALSE ); +	} +} +  static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond )  {  	struct scd *conn = data;  	if( source == -1 )  	{ -		conn->func( conn->data, NULL, cond ); +		conn->func( conn->data, 0, NULL, cond );  		g_free( conn );  		return FALSE;  	} @@ -135,7 +241,15 @@ static gboolean ssl_connected( gpointer data, gint source, b_input_condition con  	ssl_init();  	gnutls_certificate_allocate_credentials( &conn->xcred ); +	if( conn->verify && global.conf->cafile ) +	{ +		gnutls_certificate_set_x509_trust_file( conn->xcred, global.conf->cafile, GNUTLS_X509_FMT_PEM ); +		gnutls_certificate_set_verify_flags( conn->xcred, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT ); +	} +  	gnutls_init( &conn->session, GNUTLS_CLIENT ); +	if( conn->verify ) +		gnutls_session_set_ptr( conn->session, (void *) conn->hostname );  #if GNUTLS_VERSION_NUMBER < 0x020c00  	gnutls_transport_set_lowat( conn->session, 0 );  #endif @@ -151,7 +265,7 @@ static gboolean ssl_connected( gpointer data, gint source, b_input_condition con  static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond )  {  	struct scd *conn = data; -	int st; +	int st, stver;  	if( ( st = gnutls_handshake( conn->session ) ) < 0 )  	{ @@ -162,7 +276,7 @@ static gboolean ssl_handshake( gpointer data, gint source, b_input_condition con  		}  		else  		{ -			conn->func( conn->data, NULL, cond ); +			conn->func( conn->data, 0, NULL, cond );  			gnutls_deinit( conn->session );  			gnutls_certificate_free_credentials( conn->xcred ); @@ -173,11 +287,24 @@ static gboolean ssl_handshake( gpointer data, gint source, b_input_condition con  	}  	else  	{ -		/* For now we can't handle non-blocking perfectly everywhere... */ -		sock_make_blocking( conn->fd ); +		if( conn->verify && ( stver = verify_certificate_callback( conn->session ) ) != 0 ) +		{ +			conn->func( conn->data, stver, NULL, cond ); + +			gnutls_deinit( conn->session ); +			gnutls_certificate_free_credentials( conn->xcred ); +			closesocket( conn->fd ); + +			g_free( conn ); +		} +		else +		{ +			/* For now we can't handle non-blocking perfectly everywhere... */ +			sock_make_blocking( conn->fd ); -		conn->established = TRUE; -		conn->func( conn->data, conn, cond ); +			conn->established = TRUE; +			conn->func( conn->data, 0, conn, cond ); +		}  	}  	return FALSE; | 
