Add DH1080 keyexchange to the crypt module.

Close #1378
This commit is contained in:
Christophe Beauval
2017-02-09 21:04:00 +01:00
committed by Alexey Sokolov
parent 88d1e27cc6
commit cba58ca862
2 changed files with 296 additions and 7 deletions

View File

@@ -26,10 +26,9 @@
// TODO:
//
// 1) Encrypt key storage file
// 2) Secure key exchange using pub/priv keys and the DH algorithm
// 3) Some way of notifying the user that the current channel is in "encryption
// 2) Some way of notifying the user that the current channel is in "encryption
// mode" verses plain text
// 4) Temporarily disable a target (nick/chan)
// 3) Temporarily disable a target (nick/chan)
//
// NOTE: This module is currently NOT intended to secure you from your shell
// admin.
@@ -43,6 +42,9 @@
#include <znc/Chan.h>
#include <znc/User.h>
#include <znc/IRCNetwork.h>
#include <openssl/dh.h>
#include <openssl/bn.h>
#include <znc/SHA256.h>
#define REQUIRESSL 1
// To be removed in future versions
@@ -50,6 +52,126 @@
#define NICK_PREFIX_KEY "@nick-prefix@"
class CCryptMod : public CModule {
private:
/*
* As used in other implementations like KVIrc, fish10, Quassel, FiSH-irssi, ...
* all the way back to the original located at http://mircryption.sourceforge.net/Extras/McpsFishDH.zip
*/
const char* m_sPrime1080 = "FBE1022E23D213E8ACFA9AE8B9DFADA3EA6B7AC7A7B7E95AB5EB2DF858921FEADE95E6AC7BE7DE6ADBAB8A783E7AF7A7FA6A2B7BEB1E72EAE2B72F9FA2BFB2A2EFBEFAC868BADB3E828FA8BADFADA3E4CC1BE7E8AFE85E9698A783EB68FA07A77AB6AD7BEB618ACF9CA2897EB28A6189EFA07AB99A8A7FA9AE299EFA7BA66DEAFEFBEFBF0B7D8B";
/* Generate our keys once and reuse, just like ssh keys */
std::unique_ptr<DH, decltype(&DH_free)> m_pDH;
CString m_sPrivKey;
CString m_sPubKey;
#if OPENSSL_VERSION_NUMBER < 0X10100000L
static int DH_set0_pqg(DH* dh, BIGNUM* p, BIGNUM* q, BIGNUM* g) {
/* If the fields p and g in dh are nullptr, the corresponding input
* parameters MUST be non-nullptr. q may remain nullptr.
*/
if (dh == nullptr || (dh->p == nullptr && p == nullptr) || (dh->g == nullptr && g == nullptr))
return 0;
if (p != nullptr) {
BN_free(dh->p);
dh->p = p;
}
if (g != nullptr) {
BN_free(dh->g);
dh->g = g;
}
if (q != nullptr) {
BN_free(dh->q);
dh->q = q;
dh->length = BN_num_bits(q);
}
return 1;
}
static void DH_get0_key(const DH* dh, const BIGNUM** pub_key, const BIGNUM** priv_key) {
if (dh != nullptr) {
if (pub_key != nullptr)
*pub_key = dh->pub_key;
if (priv_key != nullptr)
*priv_key = dh->priv_key;
}
}
#endif
bool DH1080_gen() {
/* Generate our keys on first call */
if (m_sPrivKey.empty() || m_sPubKey.empty()) {
int len;
const BIGNUM* bPrivKey = nullptr;
const BIGNUM* bPubKey = nullptr;
BIGNUM* bPrime = nullptr;
BIGNUM* bGen = nullptr;
if (!BN_hex2bn(&bPrime, m_sPrime1080) || !BN_dec2bn(&bGen, "2") || !DH_set0_pqg(m_pDH.get(), bPrime, nullptr, bGen) || !DH_generate_key(m_pDH.get())) {
/* one of them failed */
if (bPrime != nullptr)
BN_clear_free(bPrime);
if (bGen != nullptr)
BN_clear_free(bGen);
return false;
}
/* Get our keys */
DH_get0_key(m_pDH.get(), &bPubKey, &bPrivKey);
/* Get our private key */
len = BN_num_bytes(bPrivKey);
m_sPrivKey.resize(len);
BN_bn2bin(bPrivKey, (unsigned char*)m_sPrivKey.data());
m_sPrivKey.Base64Encode();
/* Get our public key */
len = BN_num_bytes(bPubKey);
m_sPubKey.resize(len);
BN_bn2bin(bPubKey, (unsigned char*)m_sPubKey.data());
m_sPubKey.Base64Encode();
}
return true;
}
bool DH1080_comp(CString& sOtherPubKey, CString& sSecretKey) {
unsigned long len;
unsigned char* key = nullptr;
BIGNUM* bOtherPubKey = nullptr;
/* Prepare other public key */
len = sOtherPubKey.Base64Decode();
bOtherPubKey = BN_bin2bn((unsigned char*)sOtherPubKey.data(), len, nullptr);
/* Generate secret key */
key = (unsigned char*)calloc(DH_size(m_pDH.get()), 1);
if ((len = DH_compute_key(key, bOtherPubKey, m_pDH.get())) == -1) {
sSecretKey = "";
if (bOtherPubKey != nullptr)
BN_clear_free(bOtherPubKey);
if (key != nullptr)
free(key);
return false;
}
/* Get our secret key */
sSecretKey.resize(SHA256_DIGEST_SIZE);
sha256(key, len, (unsigned char*)sSecretKey.data());
sSecretKey.Base64Encode();
sSecretKey.TrimRight("=");
if (bOtherPubKey != nullptr)
BN_clear_free(bOtherPubKey);
if (key != nullptr)
free(key);
return true;
}
CString NickPrefix() {
MCString::iterator it = FindNV(NICK_PREFIX_KEY);
/*
@@ -67,8 +189,10 @@ class CCryptMod : public CModule {
return sStatusPrefix.StartsWith("*") ? "." : "*";
}
public:
MODCONSTRUCTOR(CCryptMod) {
/* MODCONSTRUCTOR(CLASS) is of form "CLASS(...) : CModule(...)" */
MODCONSTRUCTOR(CCryptMod) , m_pDH(DH_new(), DH_free) {
AddHelpCommand();
AddCommand("DelKey", static_cast<CModCommand::ModCmdFunc>(
&CCryptMod::OnDelKeyCommand),
@@ -79,9 +203,13 @@ class CCryptMod : public CModule {
AddCommand("ListKeys", static_cast<CModCommand::ModCmdFunc>(
&CCryptMod::OnListKeysCommand),
"", "List all keys");
AddCommand("KeyX", static_cast<CModCommand::ModCmdFunc>(
&CCryptMod::OnKeyXCommand),
"<Nick>", "Start a DH1080 key exchange with nick");
}
~CCryptMod() override {}
~CCryptMod() override {
}
bool OnLoad(const CString& sArgsi, CString& sMessage) override {
MCString::iterator it = FindNV(NICK_PREFIX_KEY);
@@ -151,7 +279,7 @@ class CCryptMod : public CModule {
sMessage);
GetUser()->PutUser(":" + NickPrefix() + sNickMask + " NOTICE " +
sTarget + " :" + sMessage,
NULL, GetClient());
nullptr, GetClient());
}
CString sMsg = MakeIvec() + sMessage;
@@ -187,7 +315,7 @@ class CCryptMod : public CModule {
GetUser()->PutUser(":" + NickPrefix() + sNickMask +
" PRIVMSG " + sTarget + " :\001ACTION " +
sMessage + "\001",
NULL, GetClient());
nullptr, GetClient());
}
CString sMsg = MakeIvec() + sMessage;
@@ -227,6 +355,41 @@ class CCryptMod : public CModule {
}
EModRet OnPrivNotice(CNick& Nick, CString& sMessage) override {
CString sCommand = sMessage.Token(0);
CString sOtherPubKey = sMessage.Token(1);
if ((sCommand.Equals("DH1080_INIT") || sCommand.Equals("DH1080_INIT_CBC")) && !sOtherPubKey.empty()) {
CString sSecretKey;
CString sTail = sMessage.Token(2); /* For fish10 */
/* remove trailing A */
if (sOtherPubKey.TrimSuffix("A") && DH1080_gen() && DH1080_comp(sOtherPubKey, sSecretKey)) {
PutModule("Received DH1080 public key from " + Nick.GetNick() + ", sending mine...");
PutIRC("NOTICE " + Nick.GetNick() + " :DH1080_FINISH " + m_sPubKey + "A" + (sTail.empty()?"":(" " + sTail)));
SetNV(Nick.GetNick().AsLower(), sSecretKey);
PutModule("Key for " + Nick.GetNick() + " successfully set.");
return HALT;
}
PutModule("Error in " + sCommand + " with " + Nick.GetNick() + ": " + (sSecretKey.empty()?"no secret key computed":sSecretKey));
return CONTINUE;
} else if (sCommand.Equals("DH1080_FINISH") && !sOtherPubKey.empty()) {
/*
* In theory we could get a DH1080_FINISH without us having sent a DH1080_INIT first,
* but then to have any use for the other user, they'd already have our pub key
*/
CString sSecretKey;
/* remove trailing A */
if (sOtherPubKey.TrimSuffix("A") && DH1080_gen() && DH1080_comp(sOtherPubKey, sSecretKey)) {
SetNV(Nick.GetNick().AsLower(), sSecretKey);
PutModule("Key for " + Nick.GetNick() + " successfully set.");
return HALT;
}
PutModule("Error in " + sCommand + " with " + Nick.GetNick() + ": " + (sSecretKey.empty()?"no secret key computed":sSecretKey));
return CONTINUE;
}
FilterIncoming(Nick.GetNick(), Nick, sMessage);
return CONTINUE;
}
@@ -323,6 +486,21 @@ class CCryptMod : public CModule {
}
}
void OnKeyXCommand(const CString& sCommand) {
CString sTarget = sCommand.Token(1);
if (!sTarget.empty()) {
if (DH1080_gen()) {
PutIRC("NOTICE " + sTarget + " :DH1080_INIT " + m_sPubKey + "A");
PutModule("Sent my DH1080 public key to " + sTarget + ", waiting for reply ...");
} else {
PutModule("Error generating our keys, nothing sent.");
}
} else {
PutModule("Usage: KeyX <Nick>");
}
}
void OnListKeysCommand(const CString& sCommand) {
if (BeginNV() == EndNV()) {
PutModule("You have no encryption keys set.");