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