Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

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
  • Loading branch information...
commit f578bf94244a0927de5260c69c0ef8e2058c59cd 1 parent f2e8738
@Elizafox Elizafox authored
Showing with 202 additions and 99 deletions.
  1. +1 −0  include/znc/Utils.h
  2. +201 −99 modules/sasl.cpp
View
1  include/znc/Utils.h
@@ -185,6 +185,7 @@ class CTable : protected std::vector<std::vector<CString> > {
#ifdef HAVE_LIBSSL
+#include <openssl/aes.h>
#include <openssl/blowfish.h>
#include <openssl/md5.h>
//! does Blowfish w/64 bit feedback, no padding
View
300 modules/sasl.cpp
@@ -21,6 +21,7 @@ static const struct {
{ "EXTERNAL", "TLS certificate, for use with the *cert module", false },
#ifdef HAVE_SASL_MECHANISM
{ "DH-BLOWFISH", "Secure negotiation using the DH-BLOWFISH mechanism", true },
+ { "DH-AES", "More secure negotiation using the DH-AES mechanism", true },
#endif
{ "PLAIN", "Plain text negotiation", true },
{ NULL, NULL, false }
@@ -63,13 +64,123 @@ class Mechanisms : public VCString {
unsigned int m_uiIndex;
};
+#ifdef HAVE_SASL_MECHANISM
+class DHCommon {
+public:
+ DH *dh;
+ unsigned char *secret;
+ int key_size;
+
+ DHCommon() {
+ dh = DH_new();
+ secret = NULL;
+ key_size = 0;
+ }
+
+ ~DHCommon() {
+ if (dh)
+ DH_free(dh);
+ if (secret)
+ free(secret);
+ }
+
+ bool ParseDH(const CString &sLine) {
+ /*
+ * sLine contains the prime, generator and public key of the server.
+ * We first extract this information and then we pass this to OpenSSL.
+ * OpenSSL will generate our own public and private key. Which we then
+ * use to encrypt our password
+ *
+ * sLine will look something like:
+ *
+ * base64(
+ * prime length (2 bytes)
+ * prime
+ * generator length (2 bytes)
+ * generator
+ * servers public key length (2 bytes)
+ * servers public key
+ * )
+ */
+
+ /* Decode base64 into (data, length) */
+ CString sData = sLine.Base64Decode_n();
+ const unsigned char *data = (const unsigned char*)sData.c_str();
+ CString::size_type length = sLine.size();
+
+ if (length < 2) {
+ DEBUG("sasl: No prime number");
+ return false;
+ }
+
+ /* Prime number */
+ unsigned int size = ntohs(*(uint16_t*)data);
+ data += 2;
+ length -= 2;
+
+ if (size > length) {
+ DEBUG("sasl: Extracting prime number. Invalid length");
+ return false;
+ }
+
+ dh->p = BN_bin2bn(data, size, NULL);
+ data += size;
+
+ /* Generator */
+ if (length < 2) {
+ DEBUG("sasl: No generator");
+ return false;
+ }
+
+ size = ntohs(*(uint16_t*)data);
+ data += 2;
+ length -= 2;
+
+ if (size > length) {
+ DEBUG("sasl: Extracting generator. Invalid length");
+ return false;
+ }
+
+ dh->g = BN_bin2bn(data, size, NULL);
+ data += size;
+
+ /* Server public key */
+ size = ntohs(*(uint16_t*)data);
+ data += 2;
+ length -= 2;
+
+ if (size > length) {
+ DEBUG("sasl: Extracting server public key. Invalid length");
+ return false;
+ }
+
+ BIGNUM *server_pub_key = BN_bin2bn(data, size, NULL);
+
+ /* Generate our own public/private keys */
+ if (!DH_generate_key(dh)) {
+ DEBUG("sasl: Failed to generate keys");
+ return false;
+ }
+
+ /* Compute shared secret */
+ secret = (unsigned char*)malloc(DH_size(dh));
+ if ((key_size = DH_compute_key(secret, server_pub_key, dh)) == -1) {
+ DEBUG("sasl: Failed to compute shared secret");
+ return false;
+ }
+
+ return true;
+ }
+};
+#endif
+
class CSASLMod : public CModule {
public:
MODCONSTRUCTOR(CSASLMod) {
AddCommand("Help", static_cast<CModCommand::ModCmdFunc>(&CSASLMod::PrintHelp),
"search", "Generate this output");
AddCommand("Set", static_cast<CModCommand::ModCmdFunc>(&CSASLMod::Set),
- "username password", "Set the password for DH-BLOWFISH/PLAIN");
+ "username password", "Set the password for DH-BLOWFISH/DH-AES/PLAIN");
AddCommand("Mechanism", static_cast<CModCommand::ModCmdFunc>(&CSASLMod::SetMechanismCommand),
"[mechanism[ ...]]", "Set the mechanisms to be attempted (in order)");
AddCommand("RequireAuth", static_cast<CModCommand::ModCmdFunc>(&CSASLMod::RequireAuthCommand),
@@ -177,115 +288,106 @@ class CSASLMod : public CModule {
}
#ifdef HAVE_SASL_MECHANISM
- bool AuthenticateBlowfish(const CString& sLine) {
- /*
- * sLine contains the prime, generator and public key of the server.
- * We first extract this information and then we pass this to OpenSSL.
- * OpenSSL will generate our own public and private key. Which we then
- * use to encrypt our password with blowfish.
- *
- * sLine will look something like:
- *
- * base64(
- * prime length (2 bytes)
- * prime
- * generator length (2 bytes)
- * generator
- * servers public key length (2 bytes)
- * servers public key
- * )
- *
- * Our response should look something like:
- *
- * base64(
- * our public key length (2 bytes)
- * our public key
- * sasl username + \0
- * blowfish(
- * sasl password
- * )
- * )
- */
-
- /* Decode base64 into (data, length) */
- CString sData = sLine.Base64Decode_n();
- const unsigned char *data = (const unsigned char*)sData.c_str();
- CString::size_type length = sLine.size();
+ bool AuthenticateAES(const CString& sLine) {
+ CString::size_type length;
- DH *dh = DH_new();
-
- if (length < 2) {
- DEBUG("sasl: No prime number");
- DH_free(dh);
+ DHCommon dh;
+ if (!dh.ParseDH(sLine))
return false;
- }
- /* Prime number */
- unsigned int size = ntohs(*(uint16_t*)data);
- data += 2;
- length -= 2;
+ const int len = GetNV("username").size() + GetNV("password").size() + 2;
+ const int padlen = 16 - (len % 16);
+ CString::size_type userpass_length = len + padlen;
+ unsigned char *encrypted_userpass = (unsigned char *)malloc(userpass_length);
+ unsigned char *plaintext_userpass = (unsigned char *)malloc(userpass_length);
+
+ memset(encrypted_userpass, 0, userpass_length);
+
+ /* Create plaintext message */
+ unsigned char *ptr = plaintext_userpass;
+ memcpy(ptr, GetNV("username").c_str(), GetNV("username").size() + 1);
+ ptr += GetNV("username").size() + 1;
+ memcpy(ptr, GetNV("password").c_str(), GetNV("password").size() + 1);
+ ptr += GetNV("password").size() + 1;
+ if (padlen)
+ {
+ /* Padding */
+ unsigned char randbytes[16];
+ if (!RAND_bytes(randbytes, padlen)) {
+ DEBUG("sasl: DH-AES: Unable to pad");
+ return false;
+ }
+ memcpy(ptr, randbytes, padlen);
+ }
- if (size > length) {
- DEBUG("sasl: Extracting prime number. Invalid length");
- DH_free(dh);
+ /* Create the IV
+ * It is changed during encryption for some reason - so we need to keep a copy.
+ */
+ unsigned char iv[16], iv_copy[16];
+ if (!RAND_bytes(iv, sizeof (iv))) {
+ DEBUG("sasl: DH-AES: Unable to create IV");
return false;
}
+ memcpy(iv_copy, iv, sizeof(iv));
- dh->p = BN_bin2bn(data, size, NULL);
- data += size;
+ /* Encrypt */
+ AES_KEY key;
+ AES_set_encrypt_key(dh.secret, dh.key_size * 8, &key);
+ AES_cbc_encrypt(plaintext_userpass, encrypted_userpass, userpass_length,
+ &key, iv_copy, AES_ENCRYPT);
- /* Generator */
- if (length < 2) {
- DEBUG("sasl: No generator");
- DH_free(dh);
- return false;
- }
+ free(plaintext_userpass);
- size = ntohs(*(uint16_t*)data);
- data += 2;
- length -= 2;
+ /* Build our response */
+ length = 2 + dh.key_size + sizeof(iv) + userpass_length;
+ char *response = (char *)malloc(length);
+ char *out_ptr = response;
- if (size > length) {
- DEBUG("sasl: Extracting generator. Invalid length");
- DH_free(dh);
- return false;
- }
+ /* Size of the key + key */
+ *((uint16_t *)out_ptr) = htons((uint16_t)dh.key_size);
+ out_ptr += 2;
+ BN_bn2bin(dh.dh->pub_key, (unsigned char *)out_ptr);
+ out_ptr += dh.key_size;
- dh->g = BN_bin2bn(data, size, NULL);
- data += size;
+ /* Add the IV */
+ memcpy(out_ptr, iv, sizeof(iv));
+ out_ptr += sizeof(iv);
- /* Server public key */
- size = ntohs(*(uint16_t*)data);
- data += 2;
- length -= 2;
+ /* Add encrypted userpass to the response */
+ memcpy(out_ptr, encrypted_userpass, userpass_length);
+ free(encrypted_userpass);
- if (size > length) {
- DEBUG("sasl: Extracting server public key. Invalid length");
- DH_free(dh);
- return false;
- }
+ PutIRC("AUTHENTICATE " + CString((const char *)response, length).Base64Encode_n());
- BIGNUM *server_pub_key = BN_bin2bn(data, size, NULL);
+ DEBUG(CString((const char *)response, length).Base64Encode_n());
- /* Generate our own public/private keys */
- if (!DH_generate_key(dh)) {
- DEBUG("sasl: Failed to generate keys");
- DH_free(dh);
- return false;
- }
+ free(response);
+ return true;
+ }
- /* Compute shared secret */
- unsigned char *secret = (unsigned char*)malloc(DH_size(dh));
- int key_size;
- if ((key_size = DH_compute_key(secret, server_pub_key, dh)) == -1) {
- DEBUG("sasl: Failed to compute shared secret");
- DH_free(dh);
- free(secret);
+ bool AuthenticateBlowfish(const CString& sLine) {
+ /* Encrypt our sasl password with blowfish
+ *
+ * Our response should look something like:
+ *
+ * base64(
+ * our public key length (2 bytes)
+ * our public key
+ * sasl username + \0
+ * blowfish(
+ * sasl password
+ * )
+ * )
+ */
+ CString::size_type length;
+
+ /* Our DH params */
+ DHCommon dh;
+ if (!dh.ParseDH(sLine))
return false;
- }
- /* Encrypt our sasl password with blowfish */
- // TODO for passwords with length 8, 16, 24, 32, etc. this will have 8 additional zero bytes at the end... But it works when treated as null-terminated string anyway, and if it works I don't want to touch it right now.
+ // TODO for passwords with length 8, 16, 24, 32, etc. this will have 8 additional zero bytes at the end...
+ // But it works when treated as null-terminated string anyway, and if it works I don't want to touch it right now.
CString::size_type password_length = GetNV("password").size() + (8 - (GetNV("password").size() % 8));
unsigned char *encrypted_password = (unsigned char *)malloc(password_length);
char *plaintext_password = (char *)malloc(password_length);
@@ -295,7 +397,7 @@ class CSASLMod : public CModule {
memcpy(plaintext_password, GetNV("password").c_str(), GetNV("password").size());
BF_KEY key;
- BF_set_key(&key, key_size, secret);
+ BF_set_key(&key, dh.key_size, dh.secret);
char *out_ptr = (char *)encrypted_password;
char *in_ptr = (char *)plaintext_password;
@@ -303,20 +405,18 @@ class CSASLMod : public CModule {
BF_ecb_encrypt((unsigned char *)in_ptr, (unsigned char *)out_ptr, &key, BF_ENCRYPT);
}
- free(secret);
free(plaintext_password);
/* Build our response */
- length = 2 + BN_num_bytes(dh->pub_key) + password_length + GetNV("username").size() + 1;
+ length = 2 + BN_num_bytes(dh.dh->pub_key) + password_length + GetNV("username").size() + 1;
char *response = (char *)malloc(length);
out_ptr = response;
/* Add our key to the response */
- *((uint16_t *)out_ptr) = htons((uint16_t)BN_num_bytes(dh->pub_key));
+ *((uint16_t *)out_ptr) = htons((uint16_t)BN_num_bytes(dh.dh->pub_key));
out_ptr += 2;
- BN_bn2bin(dh->pub_key, (unsigned char *)out_ptr);
- out_ptr += BN_num_bytes(dh->pub_key);
- DH_free(dh);
+ BN_bn2bin(dh.dh->pub_key, (unsigned char *)out_ptr);
+ out_ptr += BN_num_bytes(dh.dh->pub_key);
/* Add sasl username to response */
memcpy(out_ptr, GetNV("username").c_str(), GetNV("username").length() + 1); // +1 for zero byte in the end
@@ -342,6 +442,8 @@ class CSASLMod : public CModule {
#ifdef HAVE_SASL_MECHANISM
} else if (m_Mechanisms.GetCurrent().Equals("DH-BLOWFISH")) {
AuthenticateBlowfish(sLine);
+ } else if (m_Mechanisms.GetCurrent().Equals("DH-AES")) {
+ AuthenticateAES(sLine);
#endif
} else {
/* Send blank authenticate for other mechanisms (like EXTERNAL). */
Please sign in to comment.
Something went wrong with that request. Please try again.