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