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