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