78f3b095b9e4386556da2f8fefe9f25884a20e81
[quassel.git] / src / common / cipher.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-09 by the Quassel Project                          *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20 /*
21   Copyright (C) 1997 Robey Pointer <robeypointer@gmail.com>
22   Copyright (C) 2005 Ismail Donmez <ismail@kde.org>
23   Copyright (C) 2009 Travis McHenry <tmchenryaz@cox.net>
24   Copyright (C) 2009 Johannes Huber <johu@gmx.de>
25 */
26
27 #include "cipher.h"
28
29
30 Cipher::Cipher()
31 {
32   m_primeNum = QCA::BigInteger("12745216229761186769575009943944198619149164746831579719941140425076456621824834322853258804883232842877311723249782818608677050956745409379781245497526069657222703636504651898833151008222772087491045206203033063108075098874712912417029101508315117935752962862335062591404043092163187352352197487303798807791605274487594646923"); 
33   setType("blowfish");
34 }
35
36 Cipher::Cipher(QByteArray key, QString cipherType)
37 {
38   m_primeNum = QCA::BigInteger("12745216229761186769575009943944198619149164746831579719941140425076456621824834322853258804883232842877311723249782818608677050956745409379781245497526069657222703636504651898833151008222772087491045206203033063108075098874712912417029101508315117935752962862335062591404043092163187352352197487303798807791605274487594646923");
39   setKey(key);
40   setType(cipherType);
41 }
42
43 Cipher::~Cipher()
44 {}
45
46 bool Cipher::setKey(QByteArray key)
47 {
48   if(key.isEmpty())
49     return false;
50
51   if(key.mid(0,4).toLower() == "ecb:")
52   {
53     m_cbc = false;
54     m_key = key.mid(4);
55   }
56   //strip cbc: if included
57   else if(key.mid(0,4).toLower() == "cbc:")
58   {
59     m_cbc = true;
60     m_key = key.mid(4);
61   }
62   else
63   {
64 //    if(Preferences::self()->encryptionType())
65 //      m_cbc = true;
66 //    else
67     m_cbc = false;
68     m_key = key;
69   }
70   return true;
71 }
72
73 bool Cipher::setType(const QString &type)
74 {
75   //TODO check QCA::isSupported()
76   m_type = type;
77   return true;
78 }
79
80 QByteArray Cipher::decrypt(QByteArray cipherText)
81 {
82   QByteArray pfx = "";
83   bool error = false; // used to flag non cbc, seems like good practice not to parse w/o regard for set encryption type
84
85   //if we get cbc
86   if(cipherText.mid(0,5) == "+OK *")
87   {
88     //if we have cbc
89     if(m_cbc)
90       cipherText = cipherText.mid(5);
91     //if we don't
92     else
93     {
94       cipherText = cipherText.mid(5);
95       pfx = "ERROR_NONECB: ";
96       error = true;
97     }
98   }
99   //if we get ecb
100   else if(cipherText.mid(0,4) == "+OK " || cipherText.mid(0,5) == "mcps ")
101   {
102     //if we had cbc
103     if(m_cbc)
104     {
105       cipherText = (cipherText.mid(0,4) == "+OK ") ? cipherText.mid(4) : cipherText.mid(5);
106       pfx = "ERROR_NONCBC: ";
107       error = true;
108     }
109     //if we don't
110     else
111     {
112       if(cipherText.mid(0,4) == "+OK ")
113         cipherText = cipherText.mid(4);
114       else
115         cipherText = cipherText.mid(5);
116     }
117   }
118   //all other cases we fail
119   else
120     return cipherText;
121
122   QByteArray temp;
123   // (if cbc and no error we parse cbc) || (if ecb and error we parse cbc)
124   if((m_cbc && !error) || (!m_cbc && error))
125   {
126     cipherText = cipherText;
127     temp = blowfishCBC(cipherText, false);
128
129     if(temp == cipherText)
130     {
131       // kDebug("Decryption from CBC Failed");
132       return cipherText+' '+'\n';
133     }
134     else
135       cipherText = temp;
136   }
137   else
138   {
139     temp = blowfishECB(cipherText, false);
140
141     if(temp == cipherText)
142     {
143       // kDebug("Decryption from ECB Failed");
144       return cipherText+' '+'\n';
145     }
146     else
147       cipherText = temp;
148   }
149   // TODO FIXME the proper fix for this is to show encryption differently e.g. [nick] instead of <nick>
150   // don't hate me for the mircryption reference there.
151   if (cipherText.at(0) == 1)
152     pfx = "\x0";
153   cipherText = pfx+cipherText+' '+'\n'; // FIXME(??) why is there an added space here?
154   return cipherText;
155 }
156
157 QByteArray Cipher::initKeyExchange()
158 {
159   QCA::Initializer init;
160   m_tempKey = QCA::KeyGenerator().createDH(QCA::DLGroup(m_primeNum, QCA::BigInteger(2))).toDH();
161
162   if(m_tempKey.isNull())
163     return QByteArray();
164
165   QByteArray publicKey = m_tempKey.toPublicKey().toDH().y().toArray().toByteArray();
166
167   //remove leading 0
168   if(publicKey.length() > 135 && publicKey.at(0) == '\0')
169     publicKey = publicKey.mid(1);
170
171   return publicKey.toBase64().append('A');
172 }
173
174 QByteArray Cipher::parseInitKeyX(QByteArray key)
175 {
176   QCA::Initializer init;
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   bool success = setKey(sharedKey);
206
207   if(!success)
208     return QByteArray();
209
210   return publicKey.toBase64().append('A');
211 }
212
213 bool Cipher::parseFinishKeyX(QByteArray key)
214 {
215   QCA::Initializer init;
216
217   if(key.length() != 181)
218     return false;
219
220   QCA::SecureArray remoteKey = QByteArray::fromBase64(key.left(180));
221   QCA::DLGroup group(m_primeNum, QCA::BigInteger(2));
222
223   QCA::DHPublicKey remotePub(group, remoteKey);
224
225   if(remotePub.isNull())
226     return false;
227
228   if(m_tempKey.isNull())
229     return false;
230
231   QByteArray sharedKey = m_tempKey.deriveKey(remotePub).toByteArray();
232   sharedKey = QCA::Hash("sha256").hash(sharedKey).toByteArray().toBase64();
233
234   //remove trailng = because mircryption and fish think it's a swell idea.
235   while(sharedKey.endsWith('=')) sharedKey.chop(1);
236
237   bool success = setKey(sharedKey);
238
239   return success;
240 }
241
242 QByteArray Cipher::decryptTopic(QByteArray cipherText)
243 {
244   if(cipherText.mid(0,4) == "+OK ")// FiSH style topic
245     cipherText = cipherText.mid(4);
246   else if(cipherText.left(5) == "«m«")
247     cipherText = cipherText.mid(5,cipherText.length()-10);
248   else
249     return cipherText;
250
251   QByteArray temp;
252   //TODO currently no backwards sanity checks for topic, it seems to use different standards
253   //if somebody figures them out they can enable it and add "ERROR_NONECB/CBC" warnings
254   if(m_cbc)
255     temp = blowfishCBC(cipherText.mid(1), false);
256   else
257     temp = blowfishECB(cipherText, false);
258
259   if(temp == cipherText)
260   {
261     return cipherText;
262   }
263   else
264     cipherText = temp;
265
266   if(cipherText.mid(0,2) == "@@")
267     cipherText = cipherText.mid(2);
268
269   return cipherText;
270 }
271
272 bool Cipher::encrypt(QByteArray& cipherText)
273 {
274   if (cipherText.left(3) == "+p ") //don't encode if...?
275     cipherText = cipherText.mid(3);
276   else
277   {
278     if(m_cbc) //encode in ecb or cbc decide how to determine later
279     {
280       QByteArray temp = blowfishCBC(cipherText, true);
281
282       if(temp == cipherText)
283       {
284         // kDebug("CBC Encoding Failed");
285         return false;
286       }
287
288       cipherText = "+OK *" + temp;
289     }
290     else
291     {
292       QByteArray temp = blowfishECB(cipherText, true);
293
294       if(temp == cipherText)
295       {
296         // kDebug("ECB Encoding Failed");
297         return false;
298       }
299
300       cipherText = "+OK " + temp;
301      }
302   }
303   return true;
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 QByteArray Cipher::blowfishECB(QByteArray cipherText, bool direction)
345 {
346   QCA::Initializer init;
347   QByteArray temp = cipherText;
348
349   //do padding ourselves
350   if(direction)
351   {
352     while((temp.length() % 8) != 0) temp.append('\0');
353   }
354   else
355   {
356     temp = b64ToByte(temp);
357     while((temp.length() % 8) != 0) temp.append('\0');
358   }
359
360   QCA::Direction dir = (direction) ? QCA::Encode : QCA::Decode;
361   QCA::Cipher cipher(m_type, QCA::Cipher::ECB, QCA::Cipher::NoPadding, dir, m_key);
362   QByteArray temp2 = cipher.update(QCA::MemoryRegion(temp)).toByteArray();
363   temp2 += cipher.final().toByteArray();
364
365   if(!cipher.ok())
366     return cipherText;
367
368   if(direction)
369     temp2 = byteToB64(temp2);
370
371   return temp2;
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 QByteArray Cipher::b64ToByte(QByteArray text)
425 {
426   QString base64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
427   QByteArray decoded;
428   int k = -1;
429   while (k < (text.length() - 1)) {
430     int right = 0;
431     int left = 0;
432     int v = 0;
433     int w = 0;
434     int z = 0;
435
436     for (int i = 0; i < 6; i++) {
437       k++;
438       v = base64.indexOf(text.at(k));
439       right |= v << (i * 6);
440     }
441
442     for (int i = 0; i < 6; i++) {
443       k++;
444       v = base64.indexOf(text.at(k));
445       left |= v << (i * 6);
446     }
447
448     for (int i = 0; i < 4; i++) {
449       w = ((left & (0xFF << ((3 - i) * 8))));
450       z = w >> ((3 - i) * 8);
451       if(z < 0) {z = z + 256;}
452       decoded.append(z);
453     }
454
455     for (int i = 0; i < 4; i++) {
456       w = ((right & (0xFF << ((3 - i) * 8))));
457       z = w >> ((3 - i) * 8);
458       if(z < 0) {z = z + 256;}
459       decoded.append(z);
460     }
461   }
462   return decoded;
463 }
464
465