summaryrefslogtreecommitdiff
path: root/modules/extra/sasl_dh-blowfish.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'modules/extra/sasl_dh-blowfish.cpp')
-rw-r--r--modules/extra/sasl_dh-blowfish.cpp194
1 files changed, 194 insertions, 0 deletions
diff --git a/modules/extra/sasl_dh-blowfish.cpp b/modules/extra/sasl_dh-blowfish.cpp
new file mode 100644
index 000000000..e3e0eb4f9
--- /dev/null
+++ b/modules/extra/sasl_dh-blowfish.cpp
@@ -0,0 +1,194 @@
+/* RequiredLibraries: ssl,crypto */
+/* RequiredWindowsLibraries: ssleay32,libeay32 */
+
+#include "module.h"
+#include "modules/sasl.h"
+
+#include <openssl/bn.h>
+#include <openssl/dh.h>
+#include <openssl/blowfish.h>
+
+using namespace SASL;
+
+class DHBS : public Mechanism
+{
+ void Err(Session* sess, BIGNUM* key = NULL)
+ {
+ if (key)
+ BN_free(key);
+
+ sasl->Fail(sess);
+ delete sess;
+ }
+
+ public:
+ struct DHBSSession : SASL::Session
+ {
+ DH* dh;
+ DHBSSession(Mechanism *m, const Anope::string &u, DH* dh_params) : SASL::Session(m, u)
+ {
+ if (!(dh = DH_new()))
+ return;
+
+ dh->g = BN_dup(dh_params->g);
+ dh->p = BN_dup(dh_params->p);
+
+ if (!DH_generate_key(dh))
+ {
+ DH_free(dh);
+ dh = NULL;
+ }
+ }
+
+ ~DHBSSession()
+ {
+ if (dh)
+ DH_free(dh);
+ }
+ };
+
+ DH* dh_params;
+ const size_t keysize;
+ SASL::Session* CreateSession(const Anope::string &uid) override
+ {
+ return new DHBSSession(this, uid, dh_params);
+ }
+
+ DHBS(Module *o) : Mechanism(o, "DH-BLOWFISH"), keysize(256 / 8)
+ {
+ if (!(dh_params = DH_new()))
+ throw ModuleException("DH_new() failed!");
+
+ if (!DH_generate_parameters_ex(dh_params, keysize * 8, 5, NULL))
+ {
+ DH_free(dh_params);
+ throw ModuleException("Could not generate DH-params");
+ }
+ }
+
+ ~DHBS()
+ {
+ DH_free(dh_params);
+ }
+
+ void ProcessMessage(SASL::Session *session, const SASL::Message &m) override
+ {
+ DHBSSession *sess = anope_dynamic_static_cast<DHBSSession *>(session);
+
+ if (!sess->dh)
+ {
+ sasl->SendMessage(sess, "D", "A");
+ delete sess;
+ return;
+ }
+
+ if (m.type == "S")
+ {
+ // Format: [ss]<p>[ss]<g>[ss]<pub_key>
+ // Where ss is a unsigned short with the size of the key
+ const BIGNUM* dhval[] = { sess->dh->p, sess->dh->g, sess->dh->pub_key };
+
+ // Find the size of our buffer - initialized at 6 because of string size data
+ size_t size = 6;
+ for (size_t i = 0; i < 3; i++)
+ size += BN_num_bytes(dhval[i]);
+
+ // Fill in the DH data
+ std::vector<unsigned char> buffer(size);
+ for (size_t i = 0, pos = 0; i < 3; i++)
+ {
+ *reinterpret_cast<uint16_t*>(&buffer[pos]) = htons(BN_num_bytes(dhval[i]));
+ pos += 2;
+ BN_bn2bin(dhval[i], &buffer[pos]);
+ pos += BN_num_bytes(dhval[i]);
+ }
+
+ Anope::string encoded;
+ Anope::B64Encode(Anope::string(buffer.begin(), buffer.end()), encoded);
+ sasl->SendMessage(sess, "C", encoded);
+ }
+ else if (m.type == "C")
+ {
+ // Make sure we have some data - actual size check is done later
+ if (m.data.length() < 10)
+ return Err(sess);
+
+ // Format: [ss]<key><username><\0><encrypted>
+
+ Anope::string decoded;
+ Anope::B64Decode(m.data, decoded);
+
+ // As we rely on the client giving us a null terminator at the right place,
+ // let's add one extra in case the client tries to crash us
+ const size_t decodedlen = decoded.length();
+ decoded.push_back('\0');
+
+ // Make sure we have enough data for at least the key, a one letter username, and a block of data
+ if (decodedlen < keysize + 2 + 2 + 8)
+ return Err(sess);
+
+ const unsigned char* data = reinterpret_cast<const unsigned char*>(decoded.data());
+
+ // Control the size of the key
+ if (ntohs(*reinterpret_cast<const uint16_t*>(&data[0])) != keysize)
+ return Err(sess);
+
+ // Convert pubkey from binary
+ size_t pos = 2;
+ BIGNUM* pubkey = BN_bin2bn(&data[pos], keysize, NULL);
+ if (!pubkey)
+ return Err(sess);
+
+ // Find shared key
+ std::vector<unsigned char> secretkey(DH_size(sess->dh) + 1, 0);
+ if (DH_compute_key(&secretkey[0], pubkey, sess->dh) != static_cast<int>(keysize))
+ return Err(sess, pubkey);
+
+ // Set decryption key
+ BF_KEY BFKey;
+ BF_set_key(&BFKey, keysize, &secretkey[0]);
+
+ pos += keysize;
+ const Anope::string username = reinterpret_cast<const char*>(&data[pos]);
+ // Check that the username is valid, and that we have at least one block of data
+ // 2 + 1 + 8 = uint16_t size for keylen, \0 for username, 8 for one block of data
+ if (username.empty() || username.length() + keysize + 2 + 1 + 8 > decodedlen || !IRCD->IsNickValid(username))
+ return Err(sess, pubkey);
+
+ pos += username.length() + 1;
+ size_t size = decodedlen - pos;
+
+ // Blowfish data blocks are 64 bits wide - valid format?
+ if (size % 8)
+ return Err(sess, pubkey);
+
+ std::vector<char> decrypted(size + 1, 0);
+ for (size_t i = 0; i < size; i += 8)
+ BF_ecb_encrypt(&data[pos + i], reinterpret_cast<unsigned char*>(&decrypted[i]), &BFKey, BF_DECRYPT);
+
+ std::string password = &decrypted[0];
+ if (password.empty() || password.find_first_of("\r\n") != Anope::string::npos)
+ return Err(sess, pubkey);
+
+ SASL::IdentifyRequest* req = new SASL::IdentifyRequest(this->owner, m.source, username, password);
+ EventManager::Get()->Dispatch(&Event::CheckAuthentication::OnCheckAuthentication, nullptr, req);
+ req->Dispatch();
+
+ BN_free(pubkey);
+ }
+ }
+};
+
+
+class ModuleSASLDHBS : public Module
+{
+ DHBS dhbs;
+
+ public:
+ ModuleSASLDHBS(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR | EXTRA)
+ , dhbs(this)
+ {
+ }
+};
+
+MODULE_INIT(ModuleSASLDHBS)