Skip to content

Commit

Permalink
Merge pull request #321 from Elizacat/master
Browse files Browse the repository at this point in the history
Implement DH-AES password encryption scheme.
  • Loading branch information
kylef committed Apr 16, 2013
2 parents f2e8738 + f578bf9 commit ca28786
Show file tree
Hide file tree
Showing 2 changed files with 202 additions and 99 deletions.
1 change: 1 addition & 0 deletions include/znc/Utils.h
Expand Up @@ -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
Expand Down
300 changes: 201 additions & 99 deletions modules/sasl.cpp
Expand Up @@ -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 }
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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);
Expand All @@ -295,28 +397,26 @@ 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;
for (length = password_length; length; length -= 8, in_ptr += 8, out_ptr += 8) {
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
Expand All @@ -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). */
Expand Down

0 comments on commit ca28786

Please sign in to comment.