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