aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--conf.c6
-rw-r--r--conf.h1
-rw-r--r--lib/http_client.c5
-rw-r--r--lib/ssl_bogus.c2
-rw-r--r--lib/ssl_client.h15
-rw-r--r--lib/ssl_gnutls.c110
-rw-r--r--lib/ssl_nss.c20
-rw-r--r--lib/ssl_openssl.c22
-rw-r--r--protocols/jabber/io.c53
-rw-r--r--protocols/jabber/jabber.c3
-rw-r--r--protocols/jabber/jabber.h2
-rw-r--r--protocols/skype/skype.c2
12 files changed, 216 insertions, 25 deletions
diff --git a/conf.c b/conf.c
index b5993b4b..52055c35 100644
--- a/conf.c
+++ b/conf.c
@@ -66,6 +66,7 @@ conf_t *conf_load( int argc, char *argv[] )
conf->ft_max_kbps = G_MAXUINT;
conf->ft_listen = NULL;
conf->protocols = NULL;
+ conf->cafile = NULL;
proxytype = 0;
i = conf_loadini( conf, global.conf_file );
@@ -339,6 +340,11 @@ static int conf_loadini( conf_t *conf, char *file )
g_strfreev( conf->protocols );
conf->protocols = g_strsplit_set( ini->value, " \t,;", -1 );
}
+ else if( g_strcasecmp( ini->key, "cafile" ) == 0 )
+ {
+ g_free( conf->cafile );
+ conf->cafile = g_strdup( ini->value );
+ }
else
{
fprintf( stderr, "Error: Unknown setting `%s` in configuration file (line %d).\n", ini->key, ini->line );
diff --git a/conf.h b/conf.h
index f4976039..7eca09c1 100644
--- a/conf.h
+++ b/conf.h
@@ -53,6 +53,7 @@ typedef struct conf
int ft_max_kbps;
char *ft_listen;
char **protocols;
+ char *cafile;
} conf_t;
G_GNUC_MALLOC conf_t *conf_load( int argc, char *argv[] );
diff --git a/lib/http_client.c b/lib/http_client.c
index 9d986412..02e5ebbe 100644
--- a/lib/http_client.c
+++ b/lib/http_client.c
@@ -32,7 +32,7 @@
static gboolean http_connected( gpointer data, int source, b_input_condition cond );
-static gboolean http_ssl_connected( gpointer data, void *source, b_input_condition cond );
+static gboolean http_ssl_connected( gpointer data, int returncode, void *source, b_input_condition cond );
static gboolean http_incoming_data( gpointer data, int source, b_input_condition cond );
static void http_free( struct http_request *req );
@@ -169,8 +169,9 @@ error:
return FALSE;
}
-static gboolean http_ssl_connected( gpointer data, void *source, b_input_condition cond )
+static gboolean http_ssl_connected( gpointer data, int returncode, void *source, b_input_condition cond )
{
+ //The returncode is not used at the moment.
struct http_request *req = data;
if( source == NULL )
diff --git a/lib/ssl_bogus.c b/lib/ssl_bogus.c
index f4ce5d4d..e2466c19 100644
--- a/lib/ssl_bogus.c
+++ b/lib/ssl_bogus.c
@@ -55,7 +55,7 @@ int ssl_getfd( void *conn )
return( -1 );
}
-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 )
{
return NULL;
}
diff --git a/lib/ssl_client.h b/lib/ssl_client.h
index 091335c5..03355297 100644
--- a/lib/ssl_client.h
+++ b/lib/ssl_client.h
@@ -36,14 +36,25 @@
/* Some generic error codes. Especially SSL_AGAIN is important if you
want to do asynchronous I/O. */
+#define NSS_VERIFY_ERROR -2
+#define OPENSSL_VERIFY_ERROR -1
#define SSL_OK 0
#define SSL_NOHANDSHAKE 1
#define SSL_AGAIN 2
+#define VERIFY_CERT_ERROR 2
+#define VERIFY_CERT_INVALID 4
+#define VERIFY_CERT_REVOKED 8
+#define VERIFY_CERT_SIGNER_NOT_FOUND 16
+#define VERIFY_CERT_SIGNER_NOT_CA 32
+#define VERIFY_CERT_INSECURE_ALGORITHM 64
+#define VERIFY_CERT_NOT_ACTIVATED 128
+#define VERIFY_CERT_EXPIRED 256
+#define VERIFY_CERT_WRONG_HOSTNAME 512
extern int ssl_errno;
/* This is what your callback function should look like. */
-typedef gboolean (*ssl_input_function)(gpointer, void*, b_input_condition);
+typedef gboolean (*ssl_input_function)(gpointer, int, void*, b_input_condition);
/* Perform any global initialization the SSL library might need. */
@@ -56,7 +67,7 @@ G_MODULE_EXPORT void *ssl_connect( char *host, int port, ssl_input_function func
/* Start an SSL session on an existing fd. Useful for STARTTLS functionality,
for example in Jabber. */
-G_MODULE_EXPORT void *ssl_starttls( int fd, ssl_input_function func, gpointer data );
+G_MODULE_EXPORT void *ssl_starttls( int fd, char *hostname, gboolean verify, ssl_input_function func, gpointer data );
/* Obviously you need special read/write functions to read data. */
G_MODULE_EXPORT int ssl_read( void *conn, char *buf, int len );
diff --git a/lib/ssl_gnutls.c b/lib/ssl_gnutls.c
index ccab8aca..41f71f63 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;
@@ -91,7 +95,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 +103,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 +132,75 @@ 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;
+}
+
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 +208,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 +232,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 +243,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 +254,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;
diff --git a/lib/ssl_nss.c b/lib/ssl_nss.c
index ec524ca6..4dfa063d 100644
--- a/lib/ssl_nss.c
+++ b/lib/ssl_nss.c
@@ -51,6 +51,7 @@ struct scd
int fd;
PRFileDesc *prfd;
gboolean established;
+ gboolean verify;
};
static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond );
@@ -131,13 +132,14 @@ static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition
return ssl_connected( conn, conn->fd, B_EV_IO_WRITE );
}
-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 );
conn->fd = fd;
conn->func = func;
conn->data = data;
+ conn->verify = verify;
/* This function should be called via a (short) timeout instead of
directly from here, because these SSL calls are *supposed* to be
@@ -157,6 +159,18 @@ static gboolean ssl_connected( gpointer data, gint source, b_input_condition con
{
struct scd *conn = data;
+ /* Right now we don't have any verification functionality for nss so we
+ fail in case verification has been requested by the user. */
+
+ if( conn->verify )
+ {
+ conn->func( conn->data, NSS_VERIFY_ERROR, NULL, cond );
+ if( source >= 0 ) closesocket( source );
+ g_free( conn );
+
+ return FALSE;
+ }
+
if( source == -1 )
goto ssl_connected_failure;
@@ -176,12 +190,12 @@ static gboolean ssl_connected( gpointer data, gint source, b_input_condition con
conn->established = TRUE;
- conn->func( conn->data, conn, cond );
+ conn->func( conn->data, 0, conn, cond );
return FALSE;
ssl_connected_failure:
- conn->func( conn->data, NULL, cond );
+ conn->func( conn->data, 0, NULL, cond );
PR_Close( conn -> prfd );
if( source >= 0 ) closesocket( source );
diff --git a/lib/ssl_openssl.c b/lib/ssl_openssl.c
index 5f64042d..7c7f725e 100644
--- a/lib/ssl_openssl.c
+++ b/lib/ssl_openssl.c
@@ -44,6 +44,7 @@ struct scd
gpointer data;
int fd;
gboolean established;
+ gboolean verify;
int inpa;
int lasterr; /* Necessary for SSL_get_error */
@@ -81,7 +82,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 );
@@ -89,6 +90,7 @@ void *ssl_starttls( int fd, ssl_input_function func, gpointer data )
conn->func = func;
conn->data = data;
conn->inpa = -1;
+ conn->verify = verify;
/* This function should be called via a (short) timeout instead of
directly from here, because these SSL calls are *supposed* to be
@@ -116,6 +118,18 @@ static gboolean ssl_connected( gpointer data, gint source, b_input_condition con
struct scd *conn = data;
SSL_METHOD *meth;
+ /* Right now we don't have any verification functionality for openssl so we
+ fail in case verification has been requested by the user. */
+
+ if( conn->verify )
+ {
+ conn->func( conn->data, OPENSSL_VERIFY_ERROR, NULL, cond );
+ if( source >= 0 ) closesocket( source );
+ g_free( conn );
+
+ return FALSE;
+ }
+
if( source == -1 )
goto ssl_connected_failure;
@@ -140,7 +154,7 @@ static gboolean ssl_connected( gpointer data, gint source, b_input_condition con
return ssl_handshake( data, source, cond );
ssl_connected_failure:
- conn->func( conn->data, NULL, cond );
+ conn->func( conn->data, 0, NULL, cond );
if( conn->ssl )
{
@@ -168,7 +182,7 @@ static gboolean ssl_handshake( gpointer data, gint source, b_input_condition con
conn->lasterr = SSL_get_error( conn->ssl, st );
if( conn->lasterr != SSL_ERROR_WANT_READ && conn->lasterr != SSL_ERROR_WANT_WRITE )
{
- conn->func( conn->data, NULL, cond );
+ conn->func( conn->data, 0, NULL, cond );
SSL_shutdown( conn->ssl );
SSL_free( conn->ssl );
@@ -186,7 +200,7 @@ static gboolean ssl_handshake( gpointer data, gint source, b_input_condition con
conn->established = TRUE;
sock_make_blocking( conn->fd ); /* For now... */
- conn->func( conn->data, conn, cond );
+ conn->func( conn->data, 0, conn, cond );
return FALSE;
}
diff --git a/protocols/jabber/io.c b/protocols/jabber/io.c
index a28eea90..9e55e3f9 100644
--- a/protocols/jabber/io.c
+++ b/protocols/jabber/io.c
@@ -275,7 +275,7 @@ gboolean jabber_connected_plain( gpointer data, gint source, b_input_condition c
return jabber_start_stream( ic );
}
-gboolean jabber_connected_ssl( gpointer data, void *source, b_input_condition cond )
+gboolean jabber_connected_ssl( gpointer data, int returncode, void *source, b_input_condition cond )
{
struct im_connection *ic = data;
struct jabber_data *jd;
@@ -292,6 +292,43 @@ gboolean jabber_connected_ssl( gpointer data, void *source, b_input_condition co
jd->ssl = NULL;
imcb_error( ic, "Could not connect to server" );
+ if (returncode == OPENSSL_VERIFY_ERROR )
+ {
+ imcb_error( ic, "This BitlBee server is built agains the OpenSSL library." );
+ imcb_error( ic, "Unfortunately certificate verification is only supported when built against GnuTLS for now." );
+ imc_logout( ic, FALSE );
+ }
+ else if (returncode == NSS_VERIFY_ERROR )
+ {
+ imcb_error( ic, "This BitlBee server is built agains the NSS library." );
+ imcb_error( ic, "Unfortunately certificate verification is only supported when built against GnuTLS for now." );
+ imc_logout( ic, FALSE );
+ }
+ else if (returncode == VERIFY_CERT_ERROR )
+ {
+ imcb_error( ic, "An error occured during the certificate verification." );
+ imc_logout( ic, FALSE );
+ }
+ else if (returncode & VERIFY_CERT_INVALID)
+ {
+ imcb_error( ic, "Unable to verify peer's certificate." );
+ if (returncode & VERIFY_CERT_REVOKED)
+ imcb_error( ic, "The certificate has been revoked." );
+ if (returncode & VERIFY_CERT_SIGNER_NOT_FOUND)
+ imcb_error( ic, "The certificate hasn't got a known issuer." );
+ if (returncode & VERIFY_CERT_SIGNER_NOT_CA)
+ imcb_error( ic, "The certificate's issuer is not a CA." );
+ if (returncode & VERIFY_CERT_INSECURE_ALGORITHM)
+ imcb_error( ic, "The certificate uses an insecure algorithm." );
+ if (returncode & VERIFY_CERT_NOT_ACTIVATED)
+ imcb_error( ic, "The certificate has not been activated." );
+ if (returncode & VERIFY_CERT_EXPIRED)
+ imcb_error( ic, "The certificate has expired." );
+ if (returncode & VERIFY_CERT_WRONG_HOSTNAME)
+ imcb_error( ic, "The hostname specified in the certificate doesn't match the server name." );
+ imc_logout( ic, FALSE );
+ }
+ else
imc_logout( ic, TRUE );
return FALSE;
}
@@ -396,7 +433,7 @@ static xt_status jabber_pkt_proceed_tls( struct xt_node *node, gpointer data )
{
struct im_connection *ic = data;
struct jabber_data *jd = ic->proto_data;
- char *xmlns;
+ char *xmlns, *tlsname;
xmlns = xt_find_attr( node, "xmlns" );
@@ -422,7 +459,17 @@ static xt_status jabber_pkt_proceed_tls( struct xt_node *node, gpointer data )
imcb_log( ic, "Converting stream to TLS" );
jd->flags |= JFLAG_STARTTLS_DONE;
- jd->ssl = ssl_starttls( jd->fd, jabber_connected_ssl, ic );
+
+ /* If the user specified a server for the account, use this server as the
+ * hostname in the certificate verification. Else we use the domain from
+ * the username. */
+ if( ic->acc->server && *ic->acc->server )
+ tlsname = ic->acc->server;
+ else
+ tlsname = jd->server;
+
+ jd->ssl = ssl_starttls( jd->fd, tlsname, set_getbool( &ic->acc->set, "tls_verify" ),
+ jabber_connected_ssl, ic );
return XT_HANDLED;
}
diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c
index 7d9547ab..dd2f0866 100644
--- a/protocols/jabber/jabber.c
+++ b/protocols/jabber/jabber.c
@@ -81,6 +81,9 @@ static void jabber_init( account_t *acc )
s = set_add( &acc->set, "tls", "try", set_eval_tls, acc );
s->flags |= ACC_SET_OFFLINE_ONLY;
+ s = set_add( &acc->set, "tls_verify", "true", set_eval_bool, acc );
+ s->flags |= ACC_SET_OFFLINE_ONLY;
+
s = set_add( &acc->set, "sasl", "true", set_eval_bool, acc );
s->flags |= ACC_SET_OFFLINE_ONLY | SET_HIDDEN_DEFAULT;
diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h
index adf9a291..5996c301 100644
--- a/protocols/jabber/jabber.h
+++ b/protocols/jabber/jabber.h
@@ -306,7 +306,7 @@ extern const struct jabber_away_state jabber_away_state_list[];
int jabber_write_packet( struct im_connection *ic, struct xt_node *node );
int jabber_write( struct im_connection *ic, char *buf, int len );
gboolean jabber_connected_plain( gpointer data, gint source, b_input_condition cond );
-gboolean jabber_connected_ssl( gpointer data, void *source, b_input_condition cond );
+gboolean jabber_connected_ssl( gpointer data, int returncode, void *source, b_input_condition cond );
gboolean jabber_start_stream( struct im_connection *ic );
void jabber_end_stream( struct im_connection *ic );
diff --git a/protocols/skype/skype.c b/protocols/skype/skype.c
index 5b1a6c30..10f355a6 100644
--- a/protocols/skype/skype.c
+++ b/protocols/skype/skype.c
@@ -1156,7 +1156,7 @@ gboolean skype_start_stream(struct im_connection *ic)
return st;
}
-gboolean skype_connected(gpointer data, void *source, b_input_condition cond)
+gboolean skype_connected(gpointer data, int returncode, void *source, b_input_condition cond)
{
struct im_connection *ic = data;
struct skype_data *sd = ic->proto_data;