From 6cabd06fabfb24cd06ba757245961165d542b82a Mon Sep 17 00:00:00 2001
From: Elizabeth Myers <elizabeth@interlinked.me>
Date: Tue, 16 Apr 2013 01:29:18 -0500
Subject: [PATCH] Implement DH-AES encrypted password scheme.

This is superior to DH-BLOWFISH as Blowfish may suffer from certain
classes of weak keys, which is difficult to mitigate against without
regenerating DH parameters repeatedly. AES also has faced far more
scrutiny and is believed to be more secure.

Reference implementation (services-side):
https://github.com/atheme/atheme/blob/master/modules/saslserv/dh-aes.c
---
 src/plugins/irc/irc-config.c   |   5 +-
 src/plugins/irc/irc-protocol.c |   9 ++
 src/plugins/irc/irc-sasl.c     | 260 +++++++++++++++++++++++++++++++----------
 src/plugins/irc/irc-sasl.h     |   4 +
 4 files changed, 217 insertions(+), 61 deletions(-)

diff --git a/src/plugins/irc/irc-config.c b/src/plugins/irc/irc-config.c
index 8075903..943241f 100644
--- a/src/plugins/irc/irc-config.c
+++ b/src/plugins/irc/irc-config.c
@@ -1576,9 +1576,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, and \"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 de26867..f9c317d 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 db3ab9f..98eb72e 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" };
 
 
 /*
@@ -67,40 +67,21 @@ irc_sasl_mechanism_plain (const char *sasl_username, const char *sasl_password)
     return answer_base64;
 }
 
-/*
- * 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)
+static 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;
     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;
+    int ret = 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 +99,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 +111,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 +122,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,18 +134,69 @@ 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);
+    ret = 1;
+dhend:
+    if (data)
+        free (data);
+    if (data_prime_number)
+        gcry_mpi_release (data_prime_number);
+    if (data_generator_number)
+        gcry_mpi_release (data_generator_number);
+    if (data_server_pub_key)
+        gcry_mpi_release (data_server_pub_key);
+    if (pub_key)
+        gcry_mpi_release (pub_key);
+    if (priv_key)
+        gcry_mpi_release (priv_key);
+    if (secret_mpi)
+        gcry_mpi_release (secret_mpi);
+
+    return ret;
+}
+
+/*
+ * 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) == 0)
+        goto bfend;
 
     /* create password buffers (clear and crypted) */
     length_password = strlen (sasl_password) +
@@ -178,13 +210,15 @@ irc_sasl_mechanism_dh_blowfish (const char *data_base64,
     /* crypt password using blowfish */
     if (gcry_cipher_open (&gcrypt_handle, GCRY_CIPHER_BLOWFISH,
                           GCRY_CIPHER_MODE_ECB, 0) != 0)
-        goto end;
+        goto bfend;
     if (gcry_cipher_setkey (gcrypt_handle, secret_bin, length_key) != 0)
-        goto end;
+        goto bfend;
     if (gcry_cipher_encrypt (gcrypt_handle,
                              password_crypted, length_password,
                              password_clear, length_password) != 0)
-        goto end;
+        goto bfend;
+
+    gcry_cipher_close (gcrypt_handle);
 
     /*
      * build answer for server, it is concatenation of:
@@ -193,16 +227,16 @@ irc_sasl_mechanism_dh_blowfish (const char *data_base64,
      *   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;
+    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 + 1);
-    ptr_answer += length_username + 1;
+    memcpy (ptr_answer, sasl_username, length_username);
+    ptr_answer += length_username;
     memcpy (ptr_answer, password_crypted, length_password);
 
     /* encode answer to base64 */
@@ -210,9 +244,7 @@ irc_sasl_mechanism_dh_blowfish (const char *data_base64,
     if (answer_base64)
         weechat_string_encode_base64 (answer, length_answer, answer_base64);
 
-end:
-    if (data)
-        free (data);
+bfend:
     if (secret_bin)
         free (secret_bin);
     if (public_bin)
@@ -223,18 +255,128 @@ end:
         free (password_crypted);
     if (answer)
         free (answer);
-    if (data_prime_number)
-        gcry_mpi_release (data_prime_number);
-    if (data_generator_number)
-        gcry_mpi_release (data_generator_number);
-    if (data_server_pub_key)
-        gcry_mpi_release (data_server_pub_key);
-    if (pub_key)
-        gcry_mpi_release (pub_key);
-    if (priv_key)
-        gcry_mpi_release (priv_key);
-    if (secret_mpi)
-        gcry_mpi_release (secret_mpi);
+
+    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 algorithim
+     * 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 75a9690..211381e 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 */
-- 
1.8.1.5

