summary refs log tree commit diff stats
path: root/src/common
diff options
context:
space:
mode:
authorTingPing <tingping@tingping.se>2013-09-02 14:24:37 -0400
committerTingPing <tingping@tingping.se>2013-09-07 18:59:28 -0400
commita903f16c68dbcdf812d2e47e2cc3caff34d99176 (patch)
treee1b5edb0f886b91589c03870ee0e9bbdbf6d532b /src/common
parent731fd33be277ea8c37044ba96d8acd305d1cf942 (diff)
Implement BLOWFISh, AES, and EXTERNAL SASL mechanisms
Closes #657
Diffstat (limited to 'src/common')
-rw-r--r--src/common/hexchat.h10
-rw-r--r--src/common/inbound.c103
-rw-r--r--src/common/inbound.h2
-rw-r--r--src/common/proto-irc.c17
-rw-r--r--src/common/server.c14
-rw-r--r--src/common/servlist.h1
-rw-r--r--src/common/text.c3
-rw-r--r--src/common/textevents.in4
-rw-r--r--src/common/util.c237
-rw-r--r--src/common/util.h4
10 files changed, 366 insertions, 29 deletions
diff --git a/src/common/hexchat.h b/src/common/hexchat.h
index 074d5a22..3946b643 100644
--- a/src/common/hexchat.h
+++ b/src/common/hexchat.h
@@ -461,6 +461,12 @@ struct msproxy_state_t
 	unsigned char		seq_sent;		/* seq number of last packet sent.	*/
 };
 
+/* SASL Mechanisms */
+#define MECH_PLAIN 0
+#define MECH_BLOWFISH 1
+#define MECH_AES 2
+#define MECH_EXTERNAL 3
+
 typedef struct server
 {
 	/*  server control operations (in server*.c) */
@@ -598,9 +604,13 @@ typedef struct server
 	unsigned int have_sasl:1;		/* SASL capability */
 	unsigned int have_except:1;	/* ban exemptions +e */
 	unsigned int have_invite:1;	/* invite exemptions +I */
+	unsigned int have_cert:1;	/* have loaded a cert */
 	unsigned int using_cp1255:1;	/* encoding is CP1255/WINDOWS-1255? */
 	unsigned int using_irc:1;		/* encoding is "IRC" (CP1252/UTF-8 hybrid)? */
 	unsigned int use_who:1;			/* whether to use WHO command to get dcc_ip */
+	unsigned int sasl_mech;			/* mechanism for sasl auth */
+	unsigned int sent_saslauth:1;	/* have sent AUTHENICATE yet */
+	unsigned int sent_capend:1;	/* have sent CAP END yet */
 #ifdef USE_OPENSSL
 	unsigned int use_ssl:1;				  /* is server SSL capable? */
 	unsigned int accept_invalid_cert:1;/* ignore result of server's cert. verify */
diff --git a/src/common/inbound.c b/src/common/inbound.c
index bdac83f7..fca5f071 100644
--- a/src/common/inbound.c
+++ b/src/common/inbound.c
@@ -1566,8 +1566,6 @@ void
 inbound_cap_ack (server *serv, char *nick, char *extensions,
 					  const message_tags_data *tags_data)
 {
-	char *pass; /* buffer for SASL password */
-
 	EMIT_SIGNAL_TIMESTAMP (XP_TE_CAPACK, serv->server_session, nick, extensions,
 								  NULL, NULL, 0, tags_data->timestamp);
 
@@ -1603,20 +1601,25 @@ inbound_cap_ack (server *serv, char *nick, char *extensions,
 
 	if (strstr (extensions, "sasl") != NULL)
 	{
-		char *user;
-
 		serv->have_sasl = TRUE;
+		serv->sent_saslauth = FALSE;
 
-		user = (((ircnet *)serv->network)->user) 
-			? (((ircnet *)serv->network)->user) : prefs.hex_irc_user_name;
-
-		EMIT_SIGNAL_TIMESTAMP (XP_TE_SASLAUTH, serv->server_session, user, NULL,
-									  NULL,	NULL,	0,	tags_data->timestamp);
+#ifdef USE_OPENSSL
+		if (serv->loginmethod == LOGIN_SASLEXTERNAL)
+		{
+			serv->sasl_mech = MECH_EXTERNAL;
+			tcp_send_len (serv, "AUTHENTICATE EXTERNAL\r\n", 23);
+		}
+		else
+		{
+			/* default to most secure, it will fallback if not supported */
+			serv->sasl_mech = MECH_AES;
+			tcp_send_len (serv, "AUTHENTICATE DH-AES\r\n", 21);
+		}
+#else
+		serv->sasl_mech = MECH_PLAIN;
 		tcp_send_len (serv, "AUTHENTICATE PLAIN\r\n", 20);
-
-		pass = encode_sasl_pass (user, serv->password);
-		tcp_sendf (serv, "AUTHENTICATE %s\r\n", pass);
-		free (pass);
+#endif
 	}
 }
 
@@ -1687,9 +1690,9 @@ inbound_cap_ls (server *serv, char *nick, char *extensions_str,
 		}
 		
 		/* if the SASL password is set AND auth mode is set to SASL, request SASL auth */
-		if (serv->loginmethod == LOGIN_SASL
-			 && strcmp (extension, "sasl") == 0
-			 && strlen (serv->password) != 0)
+		if (!strcmp (extension, "sasl")
+			&& ((serv->loginmethod == LOGIN_SASL && strlen (serv->password) != 0)
+			|| (serv->loginmethod == LOGIN_SASLEXTERNAL && serv->have_cert)))
 		{
 			strcat (buffer, "sasl ");
 			want_cap = 1;
@@ -1710,6 +1713,7 @@ inbound_cap_ls (server *serv, char *nick, char *extensions_str,
 	if (!want_sasl)
 	{
 		/* if we use SASL, CAP END is dealt via raw numerics */
+		serv->sent_capend = TRUE;
 		tcp_send_len (serv, "CAP END\r\n", 9);
 	}
 }
@@ -1717,6 +1721,7 @@ inbound_cap_ls (server *serv, char *nick, char *extensions_str,
 void
 inbound_cap_nak (server *serv, const message_tags_data *tags_data)
 {
+	serv->sent_capend = TRUE;
 	tcp_send_len (serv, "CAP END\r\n", 9);
 }
 
@@ -1727,3 +1732,69 @@ inbound_cap_list (server *serv, char *nick, char *extensions,
 	EMIT_SIGNAL_TIMESTAMP (XP_TE_CAPACK, serv->server_session, nick, extensions,
 								  NULL, NULL, 0, tags_data->timestamp);
 }
+
+static const char *sasl_mechanisms[] =
+{
+	"PLAIN",
+	"DH-BLOWFISH",
+	"DH-AES",
+	"EXTERNAL"
+};
+
+void
+inbound_sasl_authenticate (server *serv, char *data)
+{
+		char *user, *pass = NULL;
+		const char *mech = sasl_mechanisms[serv->sasl_mech];
+
+		user = (((ircnet*)serv->network)->user)
+				? (((ircnet*)serv->network)->user) : prefs.hex_irc_user_name;
+
+		switch (serv->sasl_mech)
+		{
+		case MECH_PLAIN:
+			pass = encode_sasl_pass_plain (user, serv->password);
+			break;
+#ifdef USE_OPENSSL
+		case MECH_BLOWFISH:
+			pass = encode_sasl_pass_blowfish (user, serv->password, data);
+			break;
+		case MECH_AES:
+			pass = encode_sasl_pass_aes (user, serv->password, data);
+			break;
+		case MECH_EXTERNAL:
+			pass = g_strdup ("+");
+			break;
+#endif
+		}
+
+		if (pass == NULL)
+		{
+			/* something went wrong abort */
+			serv->sent_saslauth = TRUE; /* prevent trying PLAIN */
+			tcp_sendf (serv, "AUTHENTICATE *\r\n");
+			return;
+		}
+
+		serv->sent_saslauth = TRUE;
+		tcp_sendf (serv, "AUTHENTICATE %s\r\n", pass);
+		g_free (pass);
+
+		
+		EMIT_SIGNAL_TIMESTAMP (XP_TE_SASLAUTH, serv->server_session, user, (char*)mech,
+								NULL,	NULL,	0,	0);
+}
+
+int
+inbound_sasl_error (server *serv)
+{
+	/* If server sent 904 before we sent password,
+		* mech not support so fallback to next mech */
+	if (!serv->sent_saslauth && serv->sasl_mech != MECH_EXTERNAL && serv->sasl_mech != MECH_PLAIN)
+	{
+		serv->sasl_mech -= 1;
+		tcp_sendf (serv, "AUTHENTICATE %s\r\n", sasl_mechanisms[serv->sasl_mech]);
+		return 1;
+	}
+	return 0;
+}
diff --git a/src/common/inbound.h b/src/common/inbound.h
index cbb04890..40eeb8f6 100644
--- a/src/common/inbound.h
+++ b/src/common/inbound.h
@@ -95,6 +95,8 @@ void inbound_cap_ls (server *serv, char *nick, char *extensions,
 void inbound_cap_nak (server *serv, const message_tags_data *tags_data);
 void inbound_cap_list (server *serv, char *nick, char *extensions,
 							  const message_tags_data *tags_data);
+void inbound_sasl_authenticate (server *serv, char *data);
+int inbound_sasl_error (server *serv);
 void do_dns (session *sess, char *nick, char *host,
 				 const message_tags_data *tags_data);
 gboolean alert_match_word (char *word, char *masks);
diff --git a/src/common/proto-irc.c b/src/common/proto-irc.c
index 527a7605..a7260896 100644
--- a/src/common/proto-irc.c
+++ b/src/common/proto-irc.c
@@ -45,11 +45,11 @@
 #include "url.h"
 #include "servlist.h"
 
-
 static void
 irc_login (server *serv, char *user, char *realname)
 {
 	tcp_sendf (serv, "CAP LS\r\n");		/* start with CAP LS as Charybdis sasl.txt suggests */
+	serv->sent_capend = FALSE;	/* track if we have finished */
 
 	if (serv->password[0] && serv->loginmethod == LOGIN_PASS)
 	{
@@ -953,14 +953,20 @@ process_numeric (session * sess, int n,
 									  tags_data->timestamp);
 		break;
 	case 903:	/* successful SASL auth */
-	case 904:	/* aborted SASL auth */
+	case 904:	/* failed SASL auth */
+		if (inbound_sasl_error (serv))
+			break; /* might retry */
 	case 905:	/* failed SASL auth */
-	case 906:	/* registration completes before SASL auth */
+	case 906:	/* aborted */
 	case 907:	/* attempting to re-auth after a successful auth */
 		EMIT_SIGNAL_TIMESTAMP (XP_TE_SASLRESPONSE, serv->server_session, word[1],
 									  word[2], word[3], ++word_eol[4], 0,
 									  tags_data->timestamp);
-		tcp_send_len (serv, "CAP END\r\n", 9);
+		if (!serv->sent_capend)
+		{
+			serv->sent_capend = TRUE;
+			tcp_send_len (serv, "CAP END\r\n", 9);
+		}
 		break;
 
 	default:
@@ -1321,8 +1327,9 @@ process_named_servermsg (session *sess, char *buf, char *rawname, char *word_eol
 									  tags_data->timestamp);
 		return;
 	}
-	if (!strncmp (buf, "AUTHENTICATE +", 14))	/* omit SASL "empty" responses */
+	if (!strncmp (buf, "AUTHENTICATE", 12))
 	{
+		inbound_sasl_authenticate (sess->server, word_eol[2]);
 		return;
 	}
 
diff --git a/src/common/server.c b/src/common/server.c
index e59a7ee3..eea7ce08 100644
--- a/src/common/server.c
+++ b/src/common/server.c
@@ -1049,7 +1049,8 @@ server_cleanup (server * serv)
 #ifdef USE_OPENSSL
 	if (serv->ssl)
 	{
-		_SSL_close (serv->ssl);
+		SSL_shutdown (serv->ssl);
+		SSL_free (serv->ssl);
 		serv->ssl = NULL;
 	}
 #endif
@@ -1705,18 +1706,25 @@ server_connect (server *serv, char *hostname, int port, int no_login)
 	if (serv->use_ssl)
 	{
 		char *cert_file;
+		serv->have_cert = FALSE;
 
 		/* first try network specific cert/key */
 		cert_file = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "certs" G_DIR_SEPARATOR_S "%s.pem",
 					 get_xdir (), server_get_network (serv, TRUE));
 		if (SSL_CTX_use_certificate_file (ctx, cert_file, SSL_FILETYPE_PEM) == 1)
-			SSL_CTX_use_PrivateKey_file (ctx, cert_file, SSL_FILETYPE_PEM);
+		{
+			if (SSL_CTX_use_PrivateKey_file (ctx, cert_file, SSL_FILETYPE_PEM) == 1)
+				serv->have_cert = TRUE;
+		}
 		else
 		{
 			/* if that doesn't exist, try <config>/certs/client.pem */
 			cert_file = g_build_filename (get_xdir (), "certs", "client.pem", NULL);
 			if (SSL_CTX_use_certificate_file (ctx, cert_file, SSL_FILETYPE_PEM) == 1)
-				SSL_CTX_use_PrivateKey_file (ctx, cert_file, SSL_FILETYPE_PEM);
+			{
+				if (SSL_CTX_use_PrivateKey_file (ctx, cert_file, SSL_FILETYPE_PEM) == 1)
+					serv->have_cert = TRUE;
+			}
 		}
 		g_free (cert_file);
 	}
diff --git a/src/common/servlist.h b/src/common/servlist.h
index 45b6dad6..6d6f1bd3 100644
--- a/src/common/servlist.h
+++ b/src/common/servlist.h
@@ -79,6 +79,7 @@ extern GSList *network_list;
 #define LOGIN_PASS				7
 #define LOGIN_CHALLENGEAUTH		8
 #define LOGIN_CUSTOM			9
+#define LOGIN_SASLEXTERNAL		10
 
 #define CHALLENGEAUTH_ALGO		"HMAC-SHA-256"
 #define CHALLENGEAUTH_NICK		"Q@CServe.quakenet.org"
diff --git a/src/common/text.c b/src/common/text.c
index b6ad378d..e0cdb5ee 100644
--- a/src/common/text.c
+++ b/src/common/text.c
@@ -1297,7 +1297,8 @@ static char * const pevt_generic_channel_help[] = {
 };
 
 static char * const pevt_saslauth_help[] = {
-	N_("Username")
+	N_("Username"),
+	N_("Mechanism")
 };
 
 static char * const pevt_saslresponse_help[] = {
diff --git a/src/common/textevents.in b/src/common/textevents.in
index 52b9e2ff..3631e363 100644
--- a/src/common/textevents.in
+++ b/src/common/textevents.in
@@ -703,8 +703,8 @@ pevt_resolvinguser_help
 SASL Authenticating
 XP_TE_SASLAUTH
 pevt_saslauth_help
-%C23*%O$tAuthenticating via SASL as %C18$1%O
-1
+%C23*%O$tAuthenticating via SASL as %C18$1%O (%C24$2%O)
+2
 
 SASL Response
 XP_TE_SASLRESPONSE
diff --git a/src/common/util.c b/src/common/util.c
index 52464621..374da6e5 100644
--- a/src/common/util.c
+++ b/src/common/util.c
@@ -58,6 +58,17 @@
 #include <socks.h>
 #endif
 
+/* SASL mechanisms */
+#ifdef USE_OPENSSL
+#include <openssl/bn.h>
+#include <openssl/rand.h>
+#include <openssl/blowfish.h>
+#include <openssl/aes.h>
+#ifndef WIN32
+#include <netinet/in.h>
+#endif
+#endif
+
 #ifndef HAVE_SNPRINTF
 #define snprintf g_snprintf
 #endif
@@ -1929,7 +1940,7 @@ get_subdirs (const char *path)
 }
 
 char *
-encode_sasl_pass (char *user, char *pass)
+encode_sasl_pass_plain (char *user, char *pass)
 {
 	int authlen;
 	char *buffer;
@@ -1944,6 +1955,230 @@ encode_sasl_pass (char *user, char *pass)
 	return encoded;
 }
 
+#ifdef USE_OPENSSL
+/* Adapted from ZNC's SASL module */
+
+static int
+parse_dh (char *str, DH **dh_out, unsigned char **secret_out, int *keysize_out)
+{
+	DH *dh;
+	guchar *data, *decoded_data;
+	guchar *secret;
+	gsize data_len;
+	guint size;
+	guint16 size16;
+	BIGNUM *pubkey;
+	gint key_size;
+
+	dh = DH_new();
+	data = decoded_data = g_base64_decode (str, &data_len);
+	if (data_len < 2)
+		goto fail;
+
+	/* prime number */
+	memcpy (&size16, data, sizeof(size16));
+	size = ntohs (size16);
+	data += 2;
+	data_len -= 2;
+
+	if (size > data_len)
+		goto fail;
+
+	dh->p = BN_bin2bn (data, size, NULL);
+	data += size;
+
+	/* Generator */
+	if (data_len < 2)
+		goto fail;
+
+	memcpy (&size16, data, sizeof(size16));
+	size = ntohs (size16);
+	data += 2;
+	data_len -= 2;
+	
+	if (size > data_len)
+		goto fail;
+
+	dh->g = BN_bin2bn (data, size, NULL);
+	data += size;
+
+	/* pub key */
+	if (data_len < 2)
+		goto fail;
+
+	memcpy (&size16, data, sizeof(size16));
+	size = ntohs(size16);
+	data += 2;
+	data_len -= 2;
+
+	pubkey = BN_bin2bn (data, size, NULL);
+	if (!(DH_generate_key (dh)))
+		goto fail;
+
+	secret = (unsigned char*)malloc (DH_size(dh));
+	key_size = DH_compute_key (secret, pubkey, dh);
+	if (key_size == -1)
+		goto fail;
+
+	g_free (decoded_data);
+
+	*dh_out = dh;
+	*secret_out = secret;
+	*keysize_out = key_size;
+	return 1;
+
+fail:
+	if (decoded_data)
+		g_free (decoded_data);
+	return 0;
+}
+
+char *
+encode_sasl_pass_blowfish (char *user, char *pass, char *data)
+{
+	DH *dh;
+	char *response, *ret;
+	unsigned char *secret;
+	unsigned char *encrypted_pass;
+	char *plain_pass;
+	BF_KEY key;
+	int key_size, length;
+	int pass_len = strlen (pass) + (8 - (strlen (pass) % 8));
+	int user_len = strlen (user);
+	guint16 size16;
+	char *in_ptr, *out_ptr;
+
+	if (!parse_dh (data, &dh, &secret, &key_size))
+		return NULL;
+	BF_set_key (&key, key_size, secret);
+
+	encrypted_pass = (guchar*)malloc (pass_len);
+	memset (encrypted_pass, 0, pass_len);
+	plain_pass = (char*)malloc (pass_len);
+	memset (plain_pass, 0, pass_len);
+	memcpy (plain_pass, pass, pass_len);
+	out_ptr = (char*)encrypted_pass;
+	in_ptr = (char*)plain_pass;
+
+	for (length = pass_len; length; length -= 8, in_ptr += 8, out_ptr += 8)
+		BF_ecb_encrypt ((unsigned char*)in_ptr, (unsigned char*)out_ptr, &key, BF_ENCRYPT);
+
+	/* Create response */
+	length = 2 + BN_num_bytes (dh->pub_key) + pass_len + user_len + 1;
+	response = (char*)malloc (length);
+	out_ptr = response;
+
+	/* our key */
+	size16 = htons ((guint16)BN_num_bytes (dh->pub_key));
+	memcpy (out_ptr, &size16, sizeof(size16));
+	out_ptr += 2;
+	BN_bn2bin (dh->pub_key, (guchar*)out_ptr);
+	out_ptr += BN_num_bytes (dh->pub_key);
+
+	/* username */
+	memcpy (out_ptr, user, user_len + 1);
+	out_ptr += user_len + 1;
+
+	/* pass */
+	memcpy (out_ptr, encrypted_pass, pass_len);
+	
+	ret = g_base64_encode ((const guchar*)response, length);
+
+	DH_free (dh);
+	free (plain_pass);
+	free (encrypted_pass);
+	free (secret);
+	free (response);
+
+	return ret;
+}
+
+char *
+encode_sasl_pass_aes (char *user, char *pass, char *data)
+{
+	DH *dh;
+	AES_KEY key;
+	char *response = NULL;
+	char *out_ptr, *ret = NULL;
+	unsigned char *secret, *ptr;
+	unsigned char *encrypted_userpass, *plain_userpass;
+	int key_size, length;
+	guint16 size16;
+	unsigned char iv[16], iv_copy[16];
+	int user_len = strlen (user) + 1;
+	int pass_len = strlen (pass) + 1;
+	int len = user_len + pass_len;
+	int padlen = 16 - (len % 16);
+	int userpass_len = len + padlen;
+
+	if (!parse_dh (data, &dh, &secret, &key_size))
+		return NULL;
+
+	encrypted_userpass = (guchar*)malloc (userpass_len);
+	memset (encrypted_userpass, 0, userpass_len);
+	plain_userpass = (guchar*)malloc (userpass_len);
+	memset (plain_userpass, 0, userpass_len);
+
+	/* create message */
+	/* format of: <username>\0<password>\0<padding> */
+	ptr = plain_userpass;
+	memcpy (ptr, user, user_len);
+	ptr += user_len;
+	memcpy (ptr, pass, pass_len);
+	ptr += pass_len;
+	if (padlen)
+	{
+		/* Padding */
+		unsigned char randbytes[16];
+		if (!RAND_bytes (randbytes, padlen))
+			goto end;
+
+		memcpy (ptr, randbytes, padlen);
+	}
+
+	if (!RAND_bytes (iv, sizeof (iv)))
+		goto end;
+
+	memcpy (iv_copy, iv, sizeof(iv));
+
+	/* Encrypt */
+	AES_set_encrypt_key (secret, key_size * 8, &key);
+	AES_cbc_encrypt(plain_userpass, encrypted_userpass, userpass_len, &key, iv_copy, AES_ENCRYPT);
+
+	/* Create response */
+	/* format of:  <size pubkey><pubkey><iv (always 16 bytes)><ciphertext> */
+	length = 2 + key_size + sizeof(iv) + userpass_len;
+	response = (char*)malloc (length);
+	out_ptr = response;
+
+	/* our key */
+	size16 = htons ((guint16)key_size);
+	memcpy (out_ptr, &size16, sizeof(size16));
+	out_ptr += 2;
+	BN_bn2bin (dh->pub_key, (guchar*)out_ptr);
+	out_ptr += key_size;
+
+	/* iv */
+	memcpy (out_ptr, iv, sizeof(iv));
+	out_ptr += sizeof(iv);
+
+	/* userpass */
+	memcpy (out_ptr, encrypted_userpass, userpass_len);
+	
+	ret = g_base64_encode ((const guchar*)response, length);
+
+end:
+	DH_free (dh);
+	free (plain_userpass);
+	free (encrypted_userpass);
+	free (secret);
+	if (response)
+		free (response);
+
+	return ret;
+}
+#endif
+
 #ifdef WIN32
 int
 find_font (const char *fontname)
diff --git a/src/common/util.h b/src/common/util.h
index 0ebd89d4..6b8d359c 100644
--- a/src/common/util.h
+++ b/src/common/util.h
@@ -79,7 +79,9 @@ void canonalize_key (char *key);
 int portable_mode ();
 int unity_mode ();
 GSList *get_subdirs (const char *path);
-char *encode_sasl_pass (char *user, char *pass);
+char *encode_sasl_pass_plain (char *user, char *pass);
+char *encode_sasl_pass_blowfish (char *user, char *pass, char *data);
+char *encode_sasl_pass_aes (char *user, char *pass, char *data);
 char *challengeauth_response (char *username, char *password, char *challenge);
 
 #endif