1 /*
2  * Copyright (C) 2004-2020 ZNC, see the NOTICE file for details.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 //! @author prozac@rottenboy.com
18 //
19 // The encryption here was designed to be compatible with mircryption's CBC
20 // mode.
21 //
22 // Latest tested against:
23 // MircryptionSuite - Mircryption ver 1.19.01 (dll v1.15.01 , mirc v7.32) CBC
24 // loaded and ready.
25 //
26 // TODO:
27 //
28 // 1) Encrypt key storage file
29 // 2) Some way of notifying the user that the current channel is in "encryption
30 // mode" verses plain text
31 // 3) Temporarily disable a target (nick/chan)
32 //
33 // NOTE: This module is currently NOT intended to secure you from your shell
34 // admin.
35 //       The keys are currently stored in plain text, so anyone with access to
36 //       your account (or root) can obtain them.
37 //       It is strongly suggested that you enable SSL between ZNC and your
38 //       client otherwise the encryption stops at ZNC and gets sent to your
39 //       client in plain text.
40 //
41 
42 #include <openssl/bn.h>
43 #include <openssl/dh.h>
44 #include <znc/Chan.h>
45 #include <znc/IRCNetwork.h>
46 #include <znc/SHA256.h>
47 #include <znc/User.h>
48 
49 #define REQUIRESSL 1
50 // To be removed in future versions
51 #define NICK_PREFIX_OLD_KEY "[nick-prefix]"
52 #define NICK_PREFIX_KEY "@nick-prefix@"
53 
54 class CCryptMod : public CModule {
55   private:
56     /*
57      * As used in other implementations like KVIrc, fish10, Quassel, FiSH-irssi,
58      * ... all the way back to the original located at
59      * http://mircryption.sourceforge.net/Extras/McpsFishDH.zip
60      */
61     static constexpr const char* m_sPrime1080 =
62         "FBE1022E23D213E8ACFA9AE8B9DFADA3EA6B7AC7A7B7E95AB5EB2DF858921FEADE95E6"
63         "AC7BE7DE6ADBAB8A783E7AF7A7FA6A2B7BEB1E72EAE2B72F9FA2BFB2A2EFBEFAC868BA"
64         "DB3E828FA8BADFADA3E4CC1BE7E8AFE85E9698A783EB68FA07A77AB6AD7BEB618ACF9C"
65         "A2897EB28A6189EFA07AB99A8A7FA9AE299EFA7BA66DEAFEFBEFBF0B7D8B";
66     /* Generate our keys once and reuse, just like ssh keys */
67     std::unique_ptr<DH, decltype(&DH_free)> m_pDH;
68     CString m_sPrivKey;
69     CString m_sPubKey;
70 
71 #if OPENSSL_VERSION_NUMBER < 0X10100000L || defined(LIBRESSL_VERSION_NUMBER)
DH_set0_pqg(DH * dh,BIGNUM * p,BIGNUM * q,BIGNUM * g)72     static int DH_set0_pqg(DH* dh, BIGNUM* p, BIGNUM* q, BIGNUM* g) {
73         /* If the fields p and g in dh are nullptr, the corresponding input
74          * parameters MUST be non-nullptr.  q may remain nullptr.
75          */
76         if (dh == nullptr || (dh->p == nullptr && p == nullptr) ||
77             (dh->g == nullptr && g == nullptr))
78             return 0;
79 
80         if (p != nullptr) {
81             BN_free(dh->p);
82             dh->p = p;
83         }
84         if (g != nullptr) {
85             BN_free(dh->g);
86             dh->g = g;
87         }
88         if (q != nullptr) {
89             BN_free(dh->q);
90             dh->q = q;
91             dh->length = BN_num_bits(q);
92         }
93 
94         return 1;
95     }
96 
DH_get0_key(const DH * dh,const BIGNUM ** pub_key,const BIGNUM ** priv_key)97     static void DH_get0_key(const DH* dh, const BIGNUM** pub_key,
98                             const BIGNUM** priv_key) {
99         if (dh != nullptr) {
100             if (pub_key != nullptr) *pub_key = dh->pub_key;
101             if (priv_key != nullptr) *priv_key = dh->priv_key;
102         }
103     }
104 
105 #endif
106 
DH1080_gen()107     bool DH1080_gen() {
108         /* Generate our keys on first call */
109         if (m_sPrivKey.empty() || m_sPubKey.empty()) {
110             int len;
111             const BIGNUM* bPrivKey = nullptr;
112             const BIGNUM* bPubKey = nullptr;
113             BIGNUM* bPrime = nullptr;
114             BIGNUM* bGen = nullptr;
115 
116             if (!BN_hex2bn(&bPrime, m_sPrime1080) || !BN_dec2bn(&bGen, "2") ||
117                 !DH_set0_pqg(m_pDH.get(), bPrime, nullptr, bGen) ||
118                 !DH_generate_key(m_pDH.get())) {
119                 /* one of them failed */
120                 if (bPrime != nullptr) BN_clear_free(bPrime);
121                 if (bGen != nullptr) BN_clear_free(bGen);
122                 return false;
123             }
124 
125             /* Get our keys */
126             DH_get0_key(m_pDH.get(), &bPubKey, &bPrivKey);
127 
128             /* Get our private key */
129             len = BN_num_bytes(bPrivKey);
130             m_sPrivKey.resize(len);
131             BN_bn2bin(bPrivKey, (unsigned char*)m_sPrivKey.data());
132             m_sPrivKey.Base64Encode();
133 
134             /* Get our public key */
135             len = BN_num_bytes(bPubKey);
136             m_sPubKey.resize(len);
137             BN_bn2bin(bPubKey, (unsigned char*)m_sPubKey.data());
138             m_sPubKey.Base64Encode();
139         }
140 
141         return true;
142     }
143 
DH1080_comp(CString & sOtherPubKey,CString & sSecretKey)144     bool DH1080_comp(CString& sOtherPubKey, CString& sSecretKey) {
145         long len;
146         unsigned char* key = nullptr;
147         BIGNUM* bOtherPubKey = nullptr;
148 
149         /* Prepare other public key */
150         len = sOtherPubKey.Base64Decode();
151         bOtherPubKey =
152             BN_bin2bn((unsigned char*)sOtherPubKey.data(), len, nullptr);
153 
154         /* Generate secret key */
155         key = (unsigned char*)calloc(DH_size(m_pDH.get()), 1);
156         if ((len = DH_compute_key(key, bOtherPubKey, m_pDH.get())) == -1) {
157             sSecretKey = "";
158             if (bOtherPubKey != nullptr) BN_clear_free(bOtherPubKey);
159             if (key != nullptr) free(key);
160             return false;
161         }
162 
163         /* Get our secret key */
164         sSecretKey.resize(SHA256_DIGEST_SIZE);
165         sha256(key, len, (unsigned char*)sSecretKey.data());
166         sSecretKey.Base64Encode();
167         sSecretKey.TrimRight("=");
168 
169         if (bOtherPubKey != nullptr) BN_clear_free(bOtherPubKey);
170         if (key != nullptr) free(key);
171 
172         return true;
173     }
174 
NickPrefix()175     CString NickPrefix() {
176         MCString::iterator it = FindNV(NICK_PREFIX_KEY);
177         /*
178          * Check for different Prefixes to not confuse modules with nicknames
179          * Also check for overlap for rare cases like:
180          * SP = "*"; NP = "*s"; "tatus" sends an encrypted message appearing at
181          * "*status"
182          */
183         CString sStatusPrefix = GetUser()->GetStatusPrefix();
184         if (it != EndNV()) {
185             size_t sp = sStatusPrefix.size();
186             size_t np = it->second.size();
187             int min = std::min(sp, np);
188             if (min == 0 || sStatusPrefix.CaseCmp(it->second, min) != 0)
189                 return it->second;
190         }
191         return sStatusPrefix.StartsWith("*") ? "." : "*";
192     }
193 
194   public:
195     /* MODCONSTRUCTOR(CLASS) is of form "CLASS(...) : CModule(...)" */
MODCONSTRUCTOR(CCryptMod)196     MODCONSTRUCTOR(CCryptMod), m_pDH(DH_new(), DH_free) {
197         AddHelpCommand();
198         AddCommand("DelKey", t_d("<#chan|Nick>"),
199                    t_d("Remove a key for nick or channel"),
200                    [=](const CString& sLine) { OnDelKeyCommand(sLine); });
201         AddCommand("SetKey", t_d("<#chan|Nick> <Key>"),
202                    t_d("Set a key for nick or channel"),
203                    [=](const CString& sLine) { OnSetKeyCommand(sLine); });
204         AddCommand("ListKeys", "", t_d("List all keys"),
205                    [=](const CString& sLine) { OnListKeysCommand(sLine); });
206         AddCommand("KeyX", t_d("<Nick>"),
207                    t_d("Start a DH1080 key exchange with nick"),
208                    [=](const CString& sLine) { OnKeyXCommand(sLine); });
209         AddCommand(
210             "GetNickPrefix", "", t_d("Get the nick prefix"),
211             [=](const CString& sLine) { OnGetNickPrefixCommand(sLine); });
212         AddCommand(
213             "SetNickPrefix", t_d("[Prefix]"),
214             t_d("Set the nick prefix, with no argument it's disabled."),
215             [=](const CString& sLine) { OnSetNickPrefixCommand(sLine); });
216     }
217 
OnLoad(const CString & sArgsi,CString & sMessage)218     bool OnLoad(const CString& sArgsi, CString& sMessage) override {
219         MCString::iterator it = FindNV(NICK_PREFIX_KEY);
220         if (it == EndNV()) {
221             /* Don't have the new prefix key yet */
222             it = FindNV(NICK_PREFIX_OLD_KEY);
223             if (it != EndNV()) {
224                 SetNV(NICK_PREFIX_KEY, it->second);
225                 DelNV(NICK_PREFIX_OLD_KEY);
226             }
227         }
228         return true;
229     }
230 
OnUserTextMessage(CTextMessage & Message)231     EModRet OnUserTextMessage(CTextMessage& Message) override {
232         FilterOutgoing(Message);
233         return CONTINUE;
234     }
235 
OnUserNoticeMessage(CNoticeMessage & Message)236     EModRet OnUserNoticeMessage(CNoticeMessage& Message) override {
237         FilterOutgoing(Message);
238         return CONTINUE;
239     }
240 
OnUserActionMessage(CActionMessage & Message)241     EModRet OnUserActionMessage(CActionMessage& Message) override {
242         FilterOutgoing(Message);
243         return CONTINUE;
244     }
245 
OnUserTopicMessage(CTopicMessage & Message)246     EModRet OnUserTopicMessage(CTopicMessage& Message) override {
247         FilterOutgoing(Message);
248         return CONTINUE;
249     }
250 
OnPrivMsg(CNick & Nick,CString & sMessage)251     EModRet OnPrivMsg(CNick& Nick, CString& sMessage) override {
252         FilterIncoming(Nick.GetNick(), Nick, sMessage);
253         return CONTINUE;
254     }
255 
OnPrivNotice(CNick & Nick,CString & sMessage)256     EModRet OnPrivNotice(CNick& Nick, CString& sMessage) override {
257         CString sCommand = sMessage.Token(0);
258         CString sOtherPubKey = sMessage.Token(1);
259 
260         if ((sCommand.Equals("DH1080_INIT") ||
261              sCommand.Equals("DH1080_INIT_CBC")) &&
262             !sOtherPubKey.empty()) {
263             CString sSecretKey;
264             CString sTail = sMessage.Token(2); /* For fish10 */
265 
266             /* remove trailing A */
267             if (sOtherPubKey.TrimSuffix("A") && DH1080_gen() &&
268                 DH1080_comp(sOtherPubKey, sSecretKey)) {
269                 PutModule(
270                     t_f("Received DH1080 public key from {1}, sending mine...")(
271                         Nick.GetNick()));
272                 PutIRC("NOTICE " + Nick.GetNick() + " :DH1080_FINISH " +
273                        m_sPubKey + "A" + (sTail.empty() ? "" : (" " + sTail)));
274                 SetNV(Nick.GetNick().AsLower(), sSecretKey);
275                 PutModule(t_f("Key for {1} successfully set.")(Nick.GetNick()));
276                 return HALT;
277             }
278             PutModule(t_f("Error in {1} with {2}: {3}")(
279                 sCommand, Nick.GetNick(),
280                 (sSecretKey.empty() ? t_s("no secret key computed")
281                                     : sSecretKey)));
282             return CONTINUE;
283 
284         } else if (sCommand.Equals("DH1080_FINISH") && !sOtherPubKey.empty()) {
285             /*
286              * In theory we could get a DH1080_FINISH without us having sent a
287              * DH1080_INIT first, but then to have any use for the other user,
288              * they'd already have our pub key
289              */
290             CString sSecretKey;
291 
292             /* remove trailing A */
293             if (sOtherPubKey.TrimSuffix("A") && DH1080_gen() &&
294                 DH1080_comp(sOtherPubKey, sSecretKey)) {
295                 SetNV(Nick.GetNick().AsLower(), sSecretKey);
296                 PutModule(t_f("Key for {1} successfully set.")(Nick.GetNick()));
297                 return HALT;
298             }
299             PutModule(t_f("Error in {1} with {2}: {3}")(
300                 sCommand, Nick.GetNick(),
301                 (sSecretKey.empty() ? t_s("no secret key computed")
302                                     : sSecretKey)));
303             return CONTINUE;
304         }
305 
306         FilterIncoming(Nick.GetNick(), Nick, sMessage);
307         return CONTINUE;
308     }
309 
OnPrivAction(CNick & Nick,CString & sMessage)310     EModRet OnPrivAction(CNick& Nick, CString& sMessage) override {
311         FilterIncoming(Nick.GetNick(), Nick, sMessage);
312         return CONTINUE;
313     }
314 
OnChanMsg(CNick & Nick,CChan & Channel,CString & sMessage)315     EModRet OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage) override {
316         FilterIncoming(Channel.GetName(), Nick, sMessage);
317         return CONTINUE;
318     }
319 
OnChanNotice(CNick & Nick,CChan & Channel,CString & sMessage)320     EModRet OnChanNotice(CNick& Nick, CChan& Channel,
321                          CString& sMessage) override {
322         FilterIncoming(Channel.GetName(), Nick, sMessage);
323         return CONTINUE;
324     }
325 
OnChanAction(CNick & Nick,CChan & Channel,CString & sMessage)326     EModRet OnChanAction(CNick& Nick, CChan& Channel,
327                          CString& sMessage) override {
328         FilterIncoming(Channel.GetName(), Nick, sMessage);
329         return CONTINUE;
330     }
331 
OnTopic(CNick & Nick,CChan & Channel,CString & sMessage)332     EModRet OnTopic(CNick& Nick, CChan& Channel, CString& sMessage) override {
333         FilterIncoming(Channel.GetName(), Nick, sMessage);
334         return CONTINUE;
335     }
336 
OnNumericMessage(CNumericMessage & Message)337     EModRet OnNumericMessage(CNumericMessage& Message) override {
338         if (Message.GetCode() != 332) {
339             return CONTINUE;
340         }
341 
342         CChan* pChan = GetNetwork()->FindChan(Message.GetParam(1));
343         if (pChan) {
344             CNick* Nick = pChan->FindNick(Message.GetParam(0));
345             CString sTopic = Message.GetParam(2);
346 
347             FilterIncoming(pChan->GetName(), *Nick, sTopic);
348             Message.SetParam(2, sTopic);
349         }
350 
351         return CONTINUE;
352     }
353 
354     template <typename T>
FilterOutgoing(T & Msg)355     void FilterOutgoing(T& Msg) {
356         CString sTarget = Msg.GetTarget();
357         sTarget.TrimPrefix(NickPrefix());
358         Msg.SetTarget(sTarget);
359 
360         CString sMessage = Msg.GetText();
361 
362         if (sMessage.TrimPrefix("``")) {
363             return;
364         }
365 
366         MCString::iterator it = FindNV(sTarget.AsLower());
367         if (it != EndNV()) {
368             sMessage = MakeIvec() + sMessage;
369             sMessage.Encrypt(it->second);
370             sMessage.Base64Encode();
371             Msg.SetText("+OK *" + sMessage);
372         }
373     }
374 
FilterIncoming(const CString & sTarget,CNick & Nick,CString & sMessage)375     void FilterIncoming(const CString& sTarget, CNick& Nick,
376                         CString& sMessage) {
377         if (sMessage.TrimPrefix("+OK *")) {
378             MCString::iterator it = FindNV(sTarget.AsLower());
379 
380             if (it != EndNV()) {
381                 sMessage.Base64Decode();
382                 sMessage.Decrypt(it->second);
383                 sMessage.LeftChomp(8);
384                 sMessage = sMessage.c_str();
385                 Nick.SetNick(NickPrefix() + Nick.GetNick());
386             }
387         }
388     }
389 
OnDelKeyCommand(const CString & sCommand)390     void OnDelKeyCommand(const CString& sCommand) {
391         CString sTarget = sCommand.Token(1);
392 
393         if (!sTarget.empty()) {
394             if (DelNV(sTarget.AsLower())) {
395                 PutModule(t_f("Target [{1}] deleted")(sTarget));
396             } else {
397                 PutModule(t_f("Target [{1}] not found")(sTarget));
398             }
399         } else {
400             PutModule(t_s("Usage DelKey <#chan|Nick>"));
401         }
402     }
403 
OnSetKeyCommand(const CString & sCommand)404     void OnSetKeyCommand(const CString& sCommand) {
405         CString sTarget = sCommand.Token(1);
406         CString sKey = sCommand.Token(2, true);
407 
408         // Strip "cbc:" from beginning of string incase someone pastes directly
409         // from mircryption
410         sKey.TrimPrefix("cbc:");
411 
412         if (!sKey.empty()) {
413             SetNV(sTarget.AsLower(), sKey);
414             PutModule(
415                 t_f("Set encryption key for [{1}] to [{2}]")(sTarget, sKey));
416         } else {
417             PutModule(t_s("Usage: SetKey <#chan|Nick> <Key>"));
418         }
419     }
420 
OnKeyXCommand(const CString & sCommand)421     void OnKeyXCommand(const CString& sCommand) {
422         CString sTarget = sCommand.Token(1);
423 
424         if (!sTarget.empty()) {
425             if (DH1080_gen()) {
426                 PutIRC("NOTICE " + sTarget + " :DH1080_INIT " + m_sPubKey +
427                        "A");
428                 PutModule(t_f("Sent my DH1080 public key to {1}, waiting for reply ...")(sTarget));
429             } else {
430                 PutModule(t_s("Error generating our keys, nothing sent."));
431             }
432         } else {
433             PutModule(t_s("Usage: KeyX <Nick>"));
434         }
435     }
436 
OnGetNickPrefixCommand(const CString & sCommand)437     void OnGetNickPrefixCommand(const CString& sCommand) {
438         CString sPrefix = NickPrefix();
439         if (sPrefix.empty()) {
440             PutModule(t_s("Nick Prefix disabled."));
441         } else {
442             PutModule(t_f("Nick Prefix: {1}")(sPrefix));
443         }
444     }
445 
OnSetNickPrefixCommand(const CString & sCommand)446     void OnSetNickPrefixCommand(const CString& sCommand) {
447         CString sPrefix = sCommand.Token(1);
448 
449         if (sPrefix.StartsWith(":")) {
450             PutModule(
451                 t_s("You cannot use :, even followed by other symbols, as Nick "
452                     "Prefix."));
453         } else {
454             CString sStatusPrefix = GetUser()->GetStatusPrefix();
455             size_t sp = sStatusPrefix.size();
456             size_t np = sPrefix.size();
457             int min = std::min(sp, np);
458             if (min > 0 && sStatusPrefix.CaseCmp(sPrefix, min) == 0)
459                 PutModule(
460                     t_f("Overlap with Status Prefix ({1}), this Nick Prefix "
461                         "will not be used!")(sStatusPrefix));
462             else {
463                 SetNV(NICK_PREFIX_KEY, sPrefix);
464                 if (sPrefix.empty())
465                     PutModule(t_s("Disabling Nick Prefix."));
466                 else
467                     PutModule(t_f("Setting Nick Prefix to {1}")(sPrefix));
468             }
469         }
470     }
471 
OnListKeysCommand(const CString & sCommand)472     void OnListKeysCommand(const CString& sCommand) {
473         CTable Table;
474         Table.AddColumn(t_s("Target", "listkeys"));
475         Table.AddColumn(t_s("Key", "listkeys"));
476         Table.SetStyle(CTable::ListStyle);
477 
478         for (MCString::iterator it = BeginNV(); it != EndNV(); ++it) {
479             if (!it->first.Equals(NICK_PREFIX_KEY)) {
480                 Table.AddRow();
481                 Table.SetCell(t_s("Target", "listkeys"), it->first);
482                 Table.SetCell(t_s("Key", "listkeys"), it->second);
483             }
484         }
485         if (Table.empty())
486             PutModule(t_s("You have no encryption keys set."));
487         else
488             PutModule(Table);
489     }
490 
MakeIvec()491     CString MakeIvec() {
492         CString sRet;
493         time_t t;
494         time(&t);
495         int r = rand();
496         sRet.append((char*)&t, 4);
497         sRet.append((char*)&r, 4);
498 
499         return sRet;
500     }
501 };
502 
503 template <>
TModInfo(CModInfo & Info)504 void TModInfo<CCryptMod>(CModInfo& Info) {
505     Info.SetWikiPage("crypt");
506 }
507 
508 NETWORKMODULEDEFS(CCryptMod, t_s("Encryption for channel/private messages"))
509