Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge pull request #321 from Elizacat/master

Implement DH-AES password encryption scheme.
  • Loading branch information...
commit ca2878614f611bd1557c32ba1f0bde6f1d935ce1 2 parents f2e8738 + f578bf9
@kylef kylef 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.