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