From e0098845957d5e03eac505145c10b5313a8d760e Mon Sep 17 00:00:00 2001 From: Elizabeth Myers Date: Wed, 1 May 2013 09:59:39 +0200 Subject: [PATCH] irc: add support of "dh-aes" SASL mechanism (patch #8020) --- AUTHORS | 1 + ChangeLog | 3 +- src/plugins/irc/irc-config.c | 5 +- src/plugins/irc/irc-protocol.c | 9 + src/plugins/irc/irc-sasl.c | 326 ++++++++++++++++++++++++--------- src/plugins/irc/irc-sasl.h | 4 + 6 files changed, 257 insertions(+), 91 deletions(-) diff --git a/AUTHORS b/AUTHORS index 6ab892edd..12de797c5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -26,6 +26,7 @@ Alphabetically: * Dmitry Kobylin * Dominik Honnef * Elián Hanisch (m4v) +* Elizabeth Myers (Elizacat) * Frank Zacharias * Gu1ll4um3r0m41n * gwenn diff --git a/ChangeLog b/ChangeLog index 15745f314..98719c787 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,7 @@ WeeChat ChangeLog ================= Sébastien Helleu -v0.4.1-dev, 2013-04-28 +v0.4.1-dev, 2013-05-01 This document lists all changes for each version. @@ -51,6 +51,7 @@ Version 0.4.1 (under dev!) list with arguments inside), guile >= 2.0 is now required (bug #38350) * guile: fix crash on calls to callbacks during load of script (bug #38343) * guile: fix compilation with guile 2.0 +* irc: add support of "dh-aes" SASL mechanism (patch #8020) * irc: fix duplicate nick completion when someone rejoins the channel with same nick but a different case (bug #38841) * irc: add support of UHNAMES (capability "userhost-in-names") (task #9353) diff --git a/src/plugins/irc/irc-config.c b/src/plugins/irc/irc-config.c index 604a591da..a96f95682 100644 --- a/src/plugins/irc/irc-config.c +++ b/src/plugins/irc/irc-config.c @@ -1577,9 +1577,10 @@ irc_config_server_new_option (struct t_config_file *config_file, config_file, section, option_name, "integer", N_("mechanism for SASL authentication: \"plain\" for plain text " - "password, \"dh-blowfish\" for crypted password, \"external\" " + "password, \"dh-blowfish\" for blowfish crypted password, " + "\"dh-aes\" for AES crypted password, \"external\" " "for authentication using client side SSL cert"), - "plain|dh-blowfish|external", 0, 0, + "plain|dh-blowfish|dh-aes|external", 0, 0, default_value, value, null_value_allowed, callback_check_value, callback_check_value_data, diff --git a/src/plugins/irc/irc-protocol.c b/src/plugins/irc/irc-protocol.c index 15ce86d10..917f57a5e 100644 --- a/src/plugins/irc/irc-protocol.c +++ b/src/plugins/irc/irc-protocol.c @@ -170,6 +170,11 @@ IRC_PROTOCOL_CALLBACK(authenticate) sasl_username, sasl_password); break; + case IRC_SASL_MECHANISM_DH_AES: + answer = irc_sasl_mechanism_dh_aes (argv_eol[1], + sasl_username, + sasl_password); + break; case IRC_SASL_MECHANISM_EXTERNAL: answer = strdup ("+"); break; @@ -336,6 +341,10 @@ IRC_PROTOCOL_CALLBACK(cap) irc_server_sendf (server, 0, NULL, "AUTHENTICATE DH-BLOWFISH"); break; + case IRC_SASL_MECHANISM_DH_AES: + irc_server_sendf (server, 0, NULL, + "AUTHENTICATE DH-AES"); + break; case IRC_SASL_MECHANISM_EXTERNAL: irc_server_sendf (server, 0, NULL, "AUTHENTICATE EXTERNAL"); diff --git a/src/plugins/irc/irc-sasl.c b/src/plugins/irc/irc-sasl.c index db3ab9f7d..a410ad826 100644 --- a/src/plugins/irc/irc-sasl.c +++ b/src/plugins/irc/irc-sasl.c @@ -31,7 +31,7 @@ char *irc_sasl_mechanism_string[IRC_NUM_SASL_MECHANISMS] = -{ "plain", "dh-blowfish", "external" }; +{ "plain", "dh-blowfish", "dh-aes", "external" }; /* @@ -68,39 +68,28 @@ irc_sasl_mechanism_plain (const char *sasl_username, const char *sasl_password) } /* - * Builds answer for SASL authentication, using mechanism "DH-BLOWFISH". + * Reads key sent by server (Diffie-Hellman key exchange). * - * Argument data_base64 is a concatenation of 3 strings, each string is composed - * of 2 bytes (length of string), followed by content of string: - * 1. a prime number - * 2. a generator number - * 3. server-generated public key - * - * Note: result must be freed after use. + * Returns: + * 1: OK + * 0: error */ -char * -irc_sasl_mechanism_dh_blowfish (const char *data_base64, - const char *sasl_username, - const char *sasl_password) +int +irc_sasl_dh (const char *data_base64, + unsigned char **public_bin, unsigned char **secret_bin, + int *length_key) { - char *data, *answer, *ptr_answer, *answer_base64; - unsigned char *ptr_data, *secret_bin, *public_bin; - unsigned char *password_clear, *password_crypted; - int length_data, size, num_bits_prime_number, length_key; - int length_username, length_password, length_answer; + char *data; + unsigned char *ptr_data; + int length_data, size, num_bits_prime_number, rc; size_t num_written; gcry_mpi_t data_prime_number, data_generator_number, data_server_pub_key; gcry_mpi_t pub_key, priv_key, secret_mpi; - gcry_cipher_hd_t gcrypt_handle; + + rc = 0; data = NULL; - secret_bin = NULL; - public_bin = NULL; - password_clear = NULL; - password_crypted = NULL; - answer = NULL; - answer_base64 = NULL; data_prime_number = NULL; data_generator_number = NULL; data_server_pub_key = NULL; @@ -118,7 +107,7 @@ irc_sasl_mechanism_dh_blowfish (const char *data_base64, ptr_data += 2; length_data -= 2; if (size > length_data) - goto end; + goto dhend; data_prime_number = gcry_mpi_new (size * 8); gcry_mpi_scan (&data_prime_number, GCRYMPI_FMT_USG, ptr_data, size, NULL); num_bits_prime_number = gcry_mpi_get_nbits (data_prime_number); @@ -130,7 +119,7 @@ irc_sasl_mechanism_dh_blowfish (const char *data_base64, ptr_data += 2; length_data -= 2; if (size > length_data) - goto end; + goto dhend; data_generator_number = gcry_mpi_new (size * 8); gcry_mpi_scan (&data_generator_number, GCRYMPI_FMT_USG, ptr_data, size, NULL); ptr_data += size; @@ -141,7 +130,7 @@ irc_sasl_mechanism_dh_blowfish (const char *data_base64, ptr_data += 2; length_data -= 2; if (size > length_data) - goto end; + goto dhend; data_server_pub_key = gcry_mpi_new (size * 8); gcry_mpi_scan (&data_server_pub_key, GCRYMPI_FMT_USG, ptr_data, size, NULL); @@ -153,76 +142,23 @@ irc_sasl_mechanism_dh_blowfish (const char *data_base64, gcry_mpi_powm (pub_key, data_generator_number, priv_key, data_prime_number); /* compute secret_bin */ - length_key = num_bits_prime_number / 8; - secret_bin = malloc (length_key); + *length_key = num_bits_prime_number / 8; + *secret_bin = malloc (*length_key); secret_mpi = gcry_mpi_new (num_bits_prime_number); /* secret_mpi = (y ^ priv_key) % p */ gcry_mpi_powm (secret_mpi, data_server_pub_key, priv_key, data_prime_number); - gcry_mpi_print (GCRYMPI_FMT_USG, secret_bin, length_key, + gcry_mpi_print (GCRYMPI_FMT_USG, *secret_bin, *length_key, &num_written, secret_mpi); /* create public_bin */ - public_bin = malloc (length_key); - gcry_mpi_print (GCRYMPI_FMT_USG, public_bin, length_key, + *public_bin = malloc (*length_key); + gcry_mpi_print (GCRYMPI_FMT_USG, *public_bin, *length_key, &num_written, pub_key); + rc = 1; - /* create password buffers (clear and crypted) */ - length_password = strlen (sasl_password) + - ((8 - (strlen (sasl_password) % 8)) % 8); - password_clear = malloc (length_password); - password_crypted = malloc (length_password); - memset (password_clear, 0, length_password); - memset (password_crypted, 0, length_password); - memcpy (password_clear, sasl_password, strlen (sasl_password)); - - /* crypt password using blowfish */ - if (gcry_cipher_open (&gcrypt_handle, GCRY_CIPHER_BLOWFISH, - GCRY_CIPHER_MODE_ECB, 0) != 0) - goto end; - if (gcry_cipher_setkey (gcrypt_handle, secret_bin, length_key) != 0) - goto end; - if (gcry_cipher_encrypt (gcrypt_handle, - password_crypted, length_password, - password_clear, length_password) != 0) - goto end; - - /* - * build answer for server, it is concatenation of: - * 1. key length (2 bytes) - * 2. public key ('length_key' bytes) - * 3. sasl_username ('length_username'+1 bytes) - * 4. encrypted password ('length_password' bytes) - */ - length_username = strlen (sasl_username); - length_answer = 2 + length_key + length_username + 1 + length_password; - answer = malloc (length_answer); - ptr_answer = answer; - *((unsigned int *)ptr_answer) = htons(length_key); - ptr_answer += 2; - memcpy (ptr_answer, public_bin, length_key); - ptr_answer += length_key; - memcpy (ptr_answer, sasl_username, length_username + 1); - ptr_answer += length_username + 1; - memcpy (ptr_answer, password_crypted, length_password); - - /* encode answer to base64 */ - answer_base64 = malloc (length_answer * 4); - if (answer_base64) - weechat_string_encode_base64 (answer, length_answer, answer_base64); - -end: +dhend: if (data) free (data); - if (secret_bin) - free (secret_bin); - if (public_bin) - free (public_bin); - if (password_clear) - free (password_clear); - if (password_crypted) - free (password_crypted); - if (answer) - free (answer); if (data_prime_number) gcry_mpi_release (data_prime_number); if (data_generator_number) @@ -236,5 +172,219 @@ end: if (secret_mpi) gcry_mpi_release (secret_mpi); + return rc; +} + +/* + * Builds answer for SASL authentication, using mechanism "DH-BLOWFISH". + * + * Argument data_base64 is a concatenation of 3 strings, each string is composed + * of 2 bytes (length of string), followed by content of string: + * 1. a prime number + * 2. a generator number + * 3. server-generated public key + * + * Note: result must be freed after use. + */ +char * +irc_sasl_mechanism_dh_blowfish (const char *data_base64, + const char *sasl_username, + const char *sasl_password) +{ + char *answer, *ptr_answer, *answer_base64; + unsigned char *password_clear, *password_crypted; + int length_key, length_username, length_password, length_answer; + unsigned char *public_bin, *secret_bin; + gcry_cipher_hd_t gcrypt_handle; + + password_clear = NULL; + password_crypted = NULL; + answer = NULL; + answer_base64 = NULL; + secret_bin = NULL; + public_bin = NULL; + + if (!irc_sasl_dh (data_base64, &public_bin, &secret_bin, &length_key)) + goto bfend; + + /* create password buffers (clear and crypted) */ + length_password = strlen (sasl_password) + + ((8 - (strlen (sasl_password) % 8)) % 8); + password_clear = malloc (length_password); + password_crypted = malloc (length_password); + memset (password_clear, 0, length_password); + memset (password_crypted, 0, length_password); + memcpy (password_clear, sasl_password, strlen (sasl_password)); + + /* crypt password using blowfish */ + if (gcry_cipher_open (&gcrypt_handle, GCRY_CIPHER_BLOWFISH, + GCRY_CIPHER_MODE_ECB, 0) != 0) + goto bfend; + if (gcry_cipher_setkey (gcrypt_handle, secret_bin, length_key) != 0) + goto bfend; + if (gcry_cipher_encrypt (gcrypt_handle, + password_crypted, length_password, + password_clear, length_password) != 0) + goto bfend; + + gcry_cipher_close (gcrypt_handle); + + /* + * build answer for server, it is concatenation of: + * 1. key length (2 bytes) + * 2. public key ('length_key' bytes) + * 3. sasl_username ('length_username'+1 bytes) + * 4. encrypted password ('length_password' bytes) + */ + length_username = strlen (sasl_username) + 1; + length_answer = 2 + length_key + length_username + length_password; + answer = malloc (length_answer); + ptr_answer = answer; + *((unsigned int *)ptr_answer) = htons(length_key); + ptr_answer += 2; + memcpy (ptr_answer, public_bin, length_key); + ptr_answer += length_key; + memcpy (ptr_answer, sasl_username, length_username); + ptr_answer += length_username; + memcpy (ptr_answer, password_crypted, length_password); + + /* encode answer to base64 */ + answer_base64 = malloc (length_answer * 4); + if (answer_base64) + weechat_string_encode_base64 (answer, length_answer, answer_base64); + +bfend: + if (secret_bin) + free (secret_bin); + if (public_bin) + free (public_bin); + if (password_clear) + free (password_clear); + if (password_crypted) + free (password_crypted); + if (answer) + free (answer); + + return answer_base64; +} + +/* + * Builds answer for SASL authentication, using mechanism "DH-AES". + * + * Argument data_base64 is a concatenation of 3 strings, each string is composed + * of 2 bytes (length of string), followed by content of string: + * 1. a prime number + * 2. a generator number + * 3. server-generated public key + * + * Note: result must be freed after use. + */ +char * +irc_sasl_mechanism_dh_aes (const char *data_base64, + const char *sasl_username, + const char *sasl_password) +{ + char *answer, *ptr_answer, *answer_base64; + unsigned char *ptr_userpass, *userpass_clear, *userpass_crypted; + int length_key, length_answer; + int length_username, length_password, length_userpass; + unsigned char *public_bin, *secret_bin; + char iv[16]; + int cipher_algo; + gcry_cipher_hd_t gcrypt_handle; + + userpass_clear = NULL; + userpass_crypted = NULL; + answer = NULL; + answer_base64 = NULL; + secret_bin = NULL; + public_bin = NULL; + + if (irc_sasl_dh(data_base64, &public_bin, &secret_bin, &length_key) == 0) + goto aesend; + + /* Select cipher algorithm: key length * 8 = cipher bit size */ + switch (length_key) + { + case 32: + cipher_algo = GCRY_CIPHER_AES256; + break; + case 24: + cipher_algo = GCRY_CIPHER_AES192; + break; + case 16: + cipher_algo = GCRY_CIPHER_AES128; + break; + default: + /* Invalid bit length */ + goto aesend; + } + + /* Generate the IV */ + gcry_randomize (iv, sizeof (iv), GCRY_STRONG_RANDOM); + + /* create user/pass buffers (clear and crypted) */ + length_username = strlen (sasl_username) + 1; + length_password = strlen (sasl_password) + 1; + length_userpass = length_username + length_password + + ((16 - ((length_username + length_password) % 16)) % 16); + ptr_userpass = userpass_clear = malloc (length_userpass); + userpass_crypted = malloc (length_userpass); + memset (userpass_clear, 0, length_password); + memset (userpass_crypted, 0, length_password); + memcpy (ptr_userpass, sasl_username, length_username); + ptr_userpass += length_username; + memcpy (ptr_userpass, sasl_password, length_password); + + /* crypt password using AES in CBC mode */ + if (gcry_cipher_open (&gcrypt_handle, cipher_algo, + GCRY_CIPHER_MODE_CBC, 0) != 0) + goto aesend; + if (gcry_cipher_setkey (gcrypt_handle, secret_bin, length_key) != 0) + goto aesend; + if (gcry_cipher_setiv (gcrypt_handle, iv, sizeof(iv)) != 0) + goto aesend; + if (gcry_cipher_encrypt (gcrypt_handle, + userpass_crypted, length_userpass, + userpass_clear, length_userpass) != 0) + goto aesend; + + gcry_cipher_close (gcrypt_handle); + + /* + * build answer for server, it is concatenation of: + * 1. key length (2 bytes) + * 2. public key ('length_key' bytes) + * 3. IV (sizeof (iv) bytes) + * 4. encrypted password ('length_userpass' bytes) + */ + length_answer = 2 + length_key + sizeof (iv) + length_userpass; + answer = malloc (length_answer); + ptr_answer = answer; + *((unsigned int *)ptr_answer) = htons(length_key); + ptr_answer += 2; + memcpy (ptr_answer, public_bin, length_key); + ptr_answer += length_key; + memcpy (ptr_answer, iv, sizeof (iv)); + ptr_answer += sizeof (iv); + memcpy (ptr_answer, userpass_crypted, length_userpass); + + /* encode answer to base64 */ + answer_base64 = malloc (length_answer * 4); + if (answer_base64) + weechat_string_encode_base64 (answer, length_answer, answer_base64); + +aesend: + if (secret_bin) + free (secret_bin); + if (public_bin) + free (public_bin); + if (userpass_clear) + free (userpass_clear); + if (userpass_crypted) + free (userpass_crypted); + if (answer) + free (answer); + return answer_base64; } diff --git a/src/plugins/irc/irc-sasl.h b/src/plugins/irc/irc-sasl.h index 75a96900c..211381ef8 100644 --- a/src/plugins/irc/irc-sasl.h +++ b/src/plugins/irc/irc-sasl.h @@ -26,6 +26,7 @@ enum t_irc_sasl_mechanism { IRC_SASL_MECHANISM_PLAIN = 0, IRC_SASL_MECHANISM_DH_BLOWFISH, + IRC_SASL_MECHANISM_DH_AES, IRC_SASL_MECHANISM_EXTERNAL, /* number of SASL mechanisms */ IRC_NUM_SASL_MECHANISMS, @@ -38,5 +39,8 @@ extern char *irc_sasl_mechanism_plain (const char *sasl_username, extern char *irc_sasl_mechanism_dh_blowfish (const char *data_base64, const char *sasl_username, const char *sasl_password); +extern char *irc_sasl_mechanism_dh_aes (const char *data_base64, + const char *sasl_username, + const char *sasl_password); #endif /* __WEECHAT_IRC_SASL_H */