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
This commit is contained in:
Elizabeth Myers
2013-04-16 05:39:10 -05:00
parent f2e8738ffc
commit f578bf9424
2 changed files with 205 additions and 102 deletions

View File

@@ -185,6 +185,7 @@ protected:
#ifdef HAVE_LIBSSL
#include <openssl/aes.h>
#include <openssl/blowfish.h>
#include <openssl/md5.h>
//! does Blowfish w/64 bit feedback, no padding

View File

@@ -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 @@ private:
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,24 +288,86 @@ public:
}
#ifdef HAVE_SASL_MECHANISM
bool AuthenticateAES(const CString& sLine) {
CString::size_type length;
DHCommon dh;
if (!dh.ParseDH(sLine))
return false;
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);
}
/* 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));
/* 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);
free(plaintext_userpass);
/* Build our response */
length = 2 + dh.key_size + sizeof(iv) + userpass_length;
char *response = (char *)malloc(length);
char *out_ptr = response;
/* 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;
/* Add the IV */
memcpy(out_ptr, iv, sizeof(iv));
out_ptr += sizeof(iv);
/* Add encrypted userpass to the response */
memcpy(out_ptr, encrypted_userpass, userpass_length);
free(encrypted_userpass);
PutIRC("AUTHENTICATE " + CString((const char *)response, length).Base64Encode_n());
DEBUG(CString((const char *)response, length).Base64Encode_n());
free(response);
return true;
}
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
* )
*
/* Encrypt our sasl password with blowfish
*
* Our response should look something like:
*
* base64(
@@ -206,86 +379,15 @@ public:
* )
* )
*/
CString::size_type length;
/* 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();
DH *dh = DH_new();
if (length < 2) {
DEBUG("sasl: No prime number");
DH_free(dh);
/* Our DH params */
DHCommon dh;
if (!dh.ParseDH(sLine))
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");
DH_free(dh);
return false;
}
dh->p = BN_bin2bn(data, size, NULL);
data += size;
/* Generator */
if (length < 2) {
DEBUG("sasl: No generator");
DH_free(dh);
return false;
}
size = ntohs(*(uint16_t*)data);
data += 2;
length -= 2;
if (size > length) {
DEBUG("sasl: Extracting generator. Invalid length");
DH_free(dh);
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");
DH_free(dh);
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");
DH_free(dh);
return false;
}
/* 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);
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 @@ public:
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 @@ public:
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 @@ public:
#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). */