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