core: Limit "User is away" to 1hr or when changed
[quassel.git] / src / core / cipher.cpp
1 /*
2   This file has been derived from Konversation, the KDE IRC client.
3   You can redistribute it and/or modify it under the terms of the
4   GNU General Public License as published by the Free Software Foundation;
5   either version 2 of the License, or (at your option) any later version.
6 */
7
8 /*
9   Copyright (C) 1997 Robey Pointer <robeypointer@gmail.com>
10   Copyright (C) 2005 Ismail Donmez <ismail@kde.org>
11   Copyright (C) 2009 Travis McHenry <tmchenryaz@cox.net>
12   Copyright (C) 2009 Johannes Huber <johu@gmx.de>
13 */
14
15 #include "cipher.h"
16 #include "logmessage.h"
17
18 Cipher::Cipher()
19 {
20     m_primeNum = QCA::BigInteger("12745216229761186769575009943944198619149164746831579719941140425076456621824834322853258804883232842877311723249782818608677050956745409379781245497526069657222703636504651898833151008222772087491045206203033063108075098874712912417029101508315117935752962862335062591404043092163187352352197487303798807791605274487594646923");
21     setType("blowfish");
22 }
23
24
25 Cipher::Cipher(QByteArray key, QString cipherType)
26 {
27     m_primeNum = QCA::BigInteger("12745216229761186769575009943944198619149164746831579719941140425076456621824834322853258804883232842877311723249782818608677050956745409379781245497526069657222703636504651898833151008222772087491045206203033063108075098874712912417029101508315117935752962862335062591404043092163187352352197487303798807791605274487594646923");
28     setKey(key);
29     setType(cipherType);
30 }
31
32
33 Cipher::~Cipher()
34 {}
35
36 bool Cipher::setKey(QByteArray key)
37 {
38     if (key.isEmpty()) {
39         m_key.clear();
40         return false;
41     }
42
43     if (key.mid(0, 4).toLower() == "ecb:")
44     {
45         m_cbc = false;
46         m_key = key.mid(4);
47     }
48     //strip cbc: if included
49     else if (key.mid(0, 4).toLower() == "cbc:")
50     {
51         m_cbc = true;
52         m_key = key.mid(4);
53     }
54     else
55     {
56 //    if(Preferences::self()->encryptionType())
57 //      m_cbc = true;
58 //    else
59         m_cbc = false;
60         m_key = key;
61     }
62     return true;
63 }
64
65
66 bool Cipher::setType(const QString &type)
67 {
68     //TODO check QCA::isSupported()
69     m_type = type;
70     return true;
71 }
72
73
74 QByteArray Cipher::decrypt(QByteArray cipherText)
75 {
76     QByteArray pfx = "";
77     bool error = false; // used to flag non cbc, seems like good practice not to parse w/o regard for set encryption type
78
79     //if we get cbc
80     if (cipherText.mid(0, 5) == "+OK *")
81     {
82         //if we have cbc
83         if (m_cbc)
84             cipherText = cipherText.mid(5);
85         //if we don't
86         else
87         {
88             cipherText = cipherText.mid(5);
89             pfx = "ERROR_NONECB: ";
90             error = true;
91         }
92     }
93     //if we get ecb
94     else if (cipherText.mid(0, 4) == "+OK " || cipherText.mid(0, 5) == "mcps ")
95     {
96         //if we had cbc
97         if (m_cbc)
98         {
99             cipherText = (cipherText.mid(0, 4) == "+OK ") ? cipherText.mid(4) : cipherText.mid(5);
100             pfx = "ERROR_NONCBC: ";
101             error = true;
102         }
103         //if we don't
104         else
105         {
106             if (cipherText.mid(0, 4) == "+OK ")
107                 cipherText = cipherText.mid(4);
108             else
109                 cipherText = cipherText.mid(5);
110         }
111     }
112     //all other cases we fail
113     else
114         return cipherText;
115
116     QByteArray temp;
117     // (if cbc and no error we parse cbc) || (if ecb and error we parse cbc)
118     if ((m_cbc && !error) || (!m_cbc && error))
119     {
120         temp = blowfishCBC(cipherText, false);
121
122         if (temp == cipherText)
123         {
124             // kDebug("Decryption from CBC Failed");
125             return cipherText+' '+'\n';
126         }
127         else
128             cipherText = temp;
129     }
130     else
131     {
132         temp = blowfishECB(cipherText, false);
133
134         if (temp == cipherText)
135         {
136             // kDebug("Decryption from ECB Failed");
137             return cipherText+' '+'\n';
138         }
139         else
140             cipherText = temp;
141     }
142     // TODO FIXME the proper fix for this is to show encryption differently e.g. [nick] instead of <nick>
143     // don't hate me for the mircryption reference there.
144     if (cipherText.at(0) == 1)
145         pfx = "\x0";
146     cipherText = pfx+cipherText+' '+'\n'; // FIXME(??) why is there an added space here?
147     return cipherText;
148 }
149
150
151 QByteArray Cipher::initKeyExchange()
152 {
153     QCA::Initializer init;
154     m_tempKey = QCA::KeyGenerator().createDH(QCA::DLGroup(m_primeNum, QCA::BigInteger(2))).toDH();
155
156     if (m_tempKey.isNull())
157         return QByteArray();
158
159     QByteArray publicKey = m_tempKey.toPublicKey().toDH().y().toArray().toByteArray();
160
161     //remove leading 0
162     if (publicKey.length() > 135 && publicKey.at(0) == '\0')
163         publicKey = publicKey.mid(1);
164
165     return publicKey.toBase64().append('A');
166 }
167
168
169 QByteArray Cipher::parseInitKeyX(QByteArray key)
170 {
171     QCA::Initializer init;
172     bool isCBC = false;
173
174     if (key.endsWith(" CBC"))
175     {
176         isCBC = true;
177         key.chop(4);
178     }
179
180     if (key.length() != 181)
181         return QByteArray();
182
183     QCA::SecureArray remoteKey = QByteArray::fromBase64(key.left(180));
184     QCA::DLGroup group(m_primeNum, QCA::BigInteger(2));
185     QCA::DHPrivateKey privateKey = QCA::KeyGenerator().createDH(group).toDH();
186
187     if (privateKey.isNull())
188         return QByteArray();
189
190     QByteArray publicKey = privateKey.y().toArray().toByteArray();
191
192     //remove leading 0
193     if (publicKey.length() > 135 && publicKey.at(0) == '\0')
194         publicKey = publicKey.mid(1);
195
196     QCA::DHPublicKey remotePub(group, remoteKey);
197
198     if (remotePub.isNull())
199         return QByteArray();
200
201     QByteArray sharedKey = privateKey.deriveKey(remotePub).toByteArray();
202     sharedKey = QCA::Hash("sha256").hash(sharedKey).toByteArray().toBase64();
203
204     //remove trailing = because mircryption and fish think it's a swell idea.
205     while (sharedKey.endsWith('=')) sharedKey.chop(1);
206
207     if (isCBC)
208         sharedKey.prepend("cbc:");
209
210     bool success = setKey(sharedKey);
211
212     if (!success)
213         return QByteArray();
214
215     return publicKey.toBase64().append('A');
216 }
217
218
219 bool Cipher::parseFinishKeyX(QByteArray key)
220 {
221     QCA::Initializer init;
222
223     if (key.length() != 181)
224         return false;
225
226     QCA::SecureArray remoteKey = QByteArray::fromBase64(key.left(180));
227     QCA::DLGroup group(m_primeNum, QCA::BigInteger(2));
228
229     QCA::DHPublicKey remotePub(group, remoteKey);
230
231     if (remotePub.isNull())
232         return false;
233
234     if (m_tempKey.isNull())
235         return false;
236
237     QByteArray sharedKey = m_tempKey.deriveKey(remotePub).toByteArray();
238     sharedKey = QCA::Hash("sha256").hash(sharedKey).toByteArray().toBase64();
239
240     //remove trailng = because mircryption and fish think it's a swell idea.
241     while (sharedKey.endsWith('=')) sharedKey.chop(1);
242
243     bool success = setKey(sharedKey);
244
245     return success;
246 }
247
248
249 QByteArray Cipher::decryptTopic(QByteArray cipherText)
250 {
251     if (cipherText.mid(0, 4) == "+OK ") // FiSH style topic
252         cipherText = cipherText.mid(4);
253     else if (cipherText.left(5) == "«m«")
254         cipherText = cipherText.mid(5, cipherText.length()-10);
255     else
256         return cipherText;
257
258     QByteArray temp;
259     //TODO currently no backwards sanity checks for topic, it seems to use different standards
260     //if somebody figures them out they can enable it and add "ERROR_NONECB/CBC" warnings
261     if (m_cbc)
262         temp = blowfishCBC(cipherText.mid(1), false);
263     else
264         temp = blowfishECB(cipherText, false);
265
266     if (temp == cipherText)
267     {
268         return cipherText;
269     }
270     else
271         cipherText = temp;
272
273     if (cipherText.mid(0, 2) == "@@")
274         cipherText = cipherText.mid(2);
275
276     return cipherText;
277 }
278
279
280 bool Cipher::encrypt(QByteArray &cipherText)
281 {
282     if (cipherText.left(3) == "+p ") //don't encode if...?
283         cipherText = cipherText.mid(3);
284     else
285     {
286         if (m_cbc) //encode in ecb or cbc decide how to determine later
287         {
288             QByteArray temp = blowfishCBC(cipherText, true);
289
290             if (temp == cipherText)
291             {
292                 // kDebug("CBC Encoding Failed");
293                 return false;
294             }
295
296             cipherText = "+OK *" + temp;
297         }
298         else
299         {
300             QByteArray temp = blowfishECB(cipherText, true);
301
302             if (temp == cipherText)
303             {
304                 // kDebug("ECB Encoding Failed");
305                 return false;
306             }
307
308             cipherText = "+OK " + temp;
309         }
310     }
311     return true;
312 }
313
314
315 //THE BELOW WORKS AKA DO NOT TOUCH UNLESS YOU KNOW WHAT YOU'RE DOING
316 QByteArray Cipher::blowfishCBC(QByteArray cipherText, bool direction)
317 {
318     QCA::Initializer init;
319     QByteArray temp = cipherText;
320     if (direction)
321     {
322         // make sure cipherText is an interval of 8 bits. We do this before so that we
323         // know there's at least 8 bytes to en/decryption this ensures QCA doesn't fail
324         while ((temp.length() % 8) != 0) temp.append('\0');
325
326         QCA::InitializationVector iv(8);
327         temp.prepend(iv.toByteArray()); // prefix with 8bits of IV for mircryptions *CUSTOM* cbc implementation
328     }
329     else
330     {
331         temp = QByteArray::fromBase64(temp);
332         //supposedly nescessary if we get a truncated message also allows for decryption of 'crazy'
333         //en/decoding clients that use STANDARDIZED PADDING TECHNIQUES
334         while ((temp.length() % 8) != 0) temp.append('\0');
335     }
336
337     QCA::Direction dir = (direction) ? QCA::Encode : QCA::Decode;
338     QCA::Cipher cipher(m_type, QCA::Cipher::CBC, QCA::Cipher::NoPadding, dir, m_key, QCA::InitializationVector(QByteArray("0")));
339     QByteArray temp2 = cipher.update(QCA::MemoryRegion(temp)).toByteArray();
340     temp2 += cipher.final().toByteArray();
341
342     if (!cipher.ok())
343         return cipherText;
344
345     if (direction) //send in base64
346         temp2 = temp2.toBase64();
347     else //cut off the 8bits of IV
348         temp2 = temp2.remove(0, 8);
349
350     return temp2;
351 }
352
353
354 QByteArray Cipher::blowfishECB(QByteArray cipherText, bool direction)
355 {
356     QCA::Initializer init;
357     QByteArray temp = cipherText;
358
359     //do padding ourselves
360     if (direction)
361     {
362         while ((temp.length() % 8) != 0) temp.append('\0');
363     }
364     else
365     {
366         // ECB Blowfish encodes in blocks of 12 chars, so anything else is malformed input
367         if ((temp.length() % 12) != 0)
368             return cipherText;
369
370         temp = b64ToByte(temp);
371         while ((temp.length() % 8) != 0) temp.append('\0');
372     }
373
374     QCA::Direction dir = (direction) ? QCA::Encode : QCA::Decode;
375     QCA::Cipher cipher(m_type, QCA::Cipher::ECB, QCA::Cipher::NoPadding, dir, m_key);
376     QByteArray temp2 = cipher.update(QCA::MemoryRegion(temp)).toByteArray();
377     temp2 += cipher.final().toByteArray();
378
379     if (!cipher.ok())
380         return cipherText;
381
382     if (direction) {
383         // Sanity check
384         if ((temp2.length() % 8) != 0)
385             return cipherText;
386
387         temp2 = byteToB64(temp2);
388     }
389
390     return temp2;
391 }
392
393
394 //Custom non RFC 2045 compliant Base64 enc/dec code for mircryption / FiSH compatibility
395 QByteArray Cipher::byteToB64(QByteArray text)
396 {
397     int left = 0;
398     int right = 0;
399     int k = -1;
400     int v;
401     QString base64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
402     QByteArray encoded;
403     while (k < (text.length() - 1)) {
404         k++;
405         v = text.at(k); if (v < 0) v += 256;
406         left = v << 24;
407         k++;
408         v = text.at(k); if (v < 0) v += 256;
409         left += v << 16;
410         k++;
411         v = text.at(k); if (v < 0) v += 256;
412         left += v << 8;
413         k++;
414         v = text.at(k); if (v < 0) v += 256;
415         left += v;
416
417         k++;
418         v = text.at(k); if (v < 0) v += 256;
419         right = v << 24;
420         k++;
421         v = text.at(k); if (v < 0) v += 256;
422         right += v << 16;
423         k++;
424         v = text.at(k); if (v < 0) v += 256;
425         right += v << 8;
426         k++;
427         v = text.at(k); if (v < 0) v += 256;
428         right += v;
429
430         for (int i = 0; i < 6; i++) {
431             encoded.append(base64.at(right & 0x3F).toLatin1());
432             right = right >> 6;
433         }
434
435         //TODO make sure the .toLatin1 doesn't break anything
436         for (int i = 0; i < 6; i++) {
437             encoded.append(base64.at(left & 0x3F).toLatin1());
438             left = left >> 6;
439         }
440     }
441     return encoded;
442 }
443
444
445 QByteArray Cipher::b64ToByte(QByteArray text)
446 {
447     QString base64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
448     QByteArray decoded;
449     int k = -1;
450     while (k < (text.length() - 1)) {
451         int right = 0;
452         int left = 0;
453         int v = 0;
454         int w = 0;
455         int z = 0;
456
457         for (int i = 0; i < 6; i++) {
458             k++;
459             v = base64.indexOf(text.at(k));
460             right |= v << (i * 6);
461         }
462
463         for (int i = 0; i < 6; i++) {
464             k++;
465             v = base64.indexOf(text.at(k));
466             left |= v << (i * 6);
467         }
468
469         for (int i = 0; i < 4; i++) {
470             w = ((left & (0xFF << ((3 - i) * 8))));
471             z = w >> ((3 - i) * 8);
472             if (z < 0) { z = z + 256; }
473             decoded.append(z);
474         }
475
476         for (int i = 0; i < 4; i++) {
477             w = ((right & (0xFF << ((3 - i) * 8))));
478             z = w >> ((3 - i) * 8);
479             if (z < 0) { z = z + 256; }
480             decoded.append(z);
481         }
482     }
483     return decoded;
484 }
485
486
487 bool Cipher::neededFeaturesAvailable()
488 {
489     QCA::Initializer init;
490
491     if (QCA::isSupported("blowfish-ecb") && QCA::isSupported("blowfish-cbc") && QCA::isSupported("dh"))
492         return true;
493
494     return false;
495 }