~ubuntu-branches/ubuntu/raring/quassel/raring-proposed

« back to all changes in this revision

Viewing changes to src/core/cipher.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Scott Kitterman
  • Date: 2010-08-11 12:48:08 UTC
  • mfrom: (1.1.40 upstream)
  • Revision ID: james.westby@ubuntu.com-20100811124808-wl3zpp4gpbzmvn4t
Tags: 0.7~beta1-0ubuntu1
* New upstream beta release
  - Add libqca2-dev to build-depends for blowfish support
* Switch to source format v3 (Quilt) to use .bz2 tarball
  - Drop build-depends on quilt and update debian/rules
  - Add debian/source/format

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
 
18
Cipher::Cipher()
 
19
{
 
20
  m_primeNum = QCA::BigInteger("12745216229761186769575009943944198619149164746831579719941140425076456621824834322853258804883232842877311723249782818608677050956745409379781245497526069657222703636504651898833151008222772087491045206203033063108075098874712912417029101508315117935752962862335062591404043092163187352352197487303798807791605274487594646923");
 
21
  setType("blowfish");
 
22
}
 
23
 
 
24
Cipher::Cipher(QByteArray key, QString cipherType)
 
25
{
 
26
  m_primeNum = QCA::BigInteger("12745216229761186769575009943944198619149164746831579719941140425076456621824834322853258804883232842877311723249782818608677050956745409379781245497526069657222703636504651898833151008222772087491045206203033063108075098874712912417029101508315117935752962862335062591404043092163187352352197487303798807791605274487594646923");
 
27
  setKey(key);
 
28
  setType(cipherType);
 
29
}
 
30
 
 
31
Cipher::~Cipher()
 
32
{}
 
33
 
 
34
bool Cipher::setKey(QByteArray key)
 
35
{
 
36
  if(key.isEmpty())
 
37
    return false;
 
38
 
 
39
  if(key.mid(0,4).toLower() == "ecb:")
 
40
  {
 
41
    m_cbc = false;
 
42
    m_key = key.mid(4);
 
43
  }
 
44
  //strip cbc: if included
 
45
  else if(key.mid(0,4).toLower() == "cbc:")
 
46
  {
 
47
    m_cbc = true;
 
48
    m_key = key.mid(4);
 
49
  }
 
50
  else
 
51
  {
 
52
//    if(Preferences::self()->encryptionType())
 
53
//      m_cbc = true;
 
54
//    else
 
55
    m_cbc = false;
 
56
    m_key = key;
 
57
  }
 
58
  return true;
 
59
}
 
60
 
 
61
bool Cipher::setType(const QString &type)
 
62
{
 
63
  //TODO check QCA::isSupported()
 
64
  m_type = type;
 
65
  return true;
 
66
}
 
67
 
 
68
QByteArray Cipher::decrypt(QByteArray cipherText)
 
69
{
 
70
  QByteArray pfx = "";
 
71
  bool error = false; // used to flag non cbc, seems like good practice not to parse w/o regard for set encryption type
 
72
 
 
73
  //if we get cbc
 
74
  if(cipherText.mid(0,5) == "+OK *")
 
75
  {
 
76
    //if we have cbc
 
77
    if(m_cbc)
 
78
      cipherText = cipherText.mid(5);
 
79
    //if we don't
 
80
    else
 
81
    {
 
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
  {
 
90
    //if we had cbc
 
91
    if(m_cbc)
 
92
    {
 
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
    {
 
100
      if(cipherText.mid(0,4) == "+OK ")
 
101
        cipherText = cipherText.mid(4);
 
102
      else
 
103
        cipherText = cipherText.mid(5);
 
104
    }
 
105
  }
 
106
  //all other cases we fail
 
107
  else
 
108
    return cipherText;
 
109
 
 
110
  QByteArray temp;
 
111
  // (if cbc and no error we parse cbc) || (if ecb and error we parse cbc)
 
112
  if((m_cbc && !error) || (!m_cbc && error))
 
113
  {
 
114
    cipherText = cipherText;
 
115
    temp = blowfishCBC(cipherText, false);
 
116
 
 
117
    if(temp == cipherText)
 
118
    {
 
119
      // kDebug("Decryption from CBC Failed");
 
120
      return cipherText+' '+'\n';
 
121
    }
 
122
    else
 
123
      cipherText = temp;
 
124
  }
 
125
  else
 
126
  {
 
127
    temp = blowfishECB(cipherText, false);
 
128
 
 
129
    if(temp == cipherText)
 
130
    {
 
131
      // kDebug("Decryption from ECB Failed");
 
132
      return cipherText+' '+'\n';
 
133
    }
 
134
    else
 
135
      cipherText = temp;
 
136
  }
 
137
  // TODO FIXME the proper fix for this is to show encryption differently e.g. [nick] instead of <nick>
 
138
  // don't hate me for the mircryption reference there.
 
139
  if (cipherText.at(0) == 1)
 
140
    pfx = "\x0";
 
141
  cipherText = pfx+cipherText+' '+'\n'; // FIXME(??) why is there an added space here?
 
142
  return cipherText;
 
143
}
 
144
 
 
145
QByteArray Cipher::initKeyExchange()
 
146
{
 
147
  QCA::Initializer init;
 
148
  m_tempKey = QCA::KeyGenerator().createDH(QCA::DLGroup(m_primeNum, QCA::BigInteger(2))).toDH();
 
149
 
 
150
  if(m_tempKey.isNull())
 
151
    return QByteArray();
 
152
 
 
153
  QByteArray publicKey = m_tempKey.toPublicKey().toDH().y().toArray().toByteArray();
 
154
 
 
155
  //remove leading 0
 
156
  if(publicKey.length() > 135 && publicKey.at(0) == '\0')
 
157
    publicKey = publicKey.mid(1);
 
158
 
 
159
  return publicKey.toBase64().append('A');
 
160
}
 
161
 
 
162
QByteArray Cipher::parseInitKeyX(QByteArray key)
 
163
{
 
164
  QCA::Initializer init;
 
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('=')) sharedKey.chop(1);
 
192
 
 
193
  bool success = setKey(sharedKey);
 
194
 
 
195
  if(!success)
 
196
    return QByteArray();
 
197
 
 
198
  return publicKey.toBase64().append('A');
 
199
}
 
200
 
 
201
bool Cipher::parseFinishKeyX(QByteArray key)
 
202
{
 
203
  QCA::Initializer init;
 
204
 
 
205
  if(key.length() != 181)
 
206
    return false;
 
207
 
 
208
  QCA::SecureArray remoteKey = QByteArray::fromBase64(key.left(180));
 
209
  QCA::DLGroup group(m_primeNum, QCA::BigInteger(2));
 
210
 
 
211
  QCA::DHPublicKey remotePub(group, remoteKey);
 
212
 
 
213
  if(remotePub.isNull())
 
214
    return false;
 
215
 
 
216
  if(m_tempKey.isNull())
 
217
    return false;
 
218
 
 
219
  QByteArray sharedKey = m_tempKey.deriveKey(remotePub).toByteArray();
 
220
  sharedKey = QCA::Hash("sha256").hash(sharedKey).toByteArray().toBase64();
 
221
 
 
222
  //remove trailng = because mircryption and fish think it's a swell idea.
 
223
  while(sharedKey.endsWith('=')) sharedKey.chop(1);
 
224
 
 
225
  bool success = setKey(sharedKey);
 
226
 
 
227
  return success;
 
228
}
 
229
 
 
230
QByteArray Cipher::decryptTopic(QByteArray cipherText)
 
231
{
 
232
  if(cipherText.mid(0,4) == "+OK ")// FiSH style topic
 
233
    cipherText = cipherText.mid(4);
 
234
  else if(cipherText.left(5) == "«m«")
 
235
    cipherText = cipherText.mid(5,cipherText.length()-10);
 
236
  else
 
237
    return cipherText;
 
238
 
 
239
  QByteArray temp;
 
240
  //TODO currently no backwards sanity checks for topic, it seems to use different standards
 
241
  //if somebody figures them out they can enable it and add "ERROR_NONECB/CBC" warnings
 
242
  if(m_cbc)
 
243
    temp = blowfishCBC(cipherText.mid(1), false);
 
244
  else
 
245
    temp = blowfishECB(cipherText, false);
 
246
 
 
247
  if(temp == cipherText)
 
248
  {
 
249
    return cipherText;
 
250
  }
 
251
  else
 
252
    cipherText = temp;
 
253
 
 
254
  if(cipherText.mid(0,2) == "@@")
 
255
    cipherText = cipherText.mid(2);
 
256
 
 
257
  return cipherText;
 
258
}
 
259
 
 
260
bool Cipher::encrypt(QByteArray& cipherText)
 
261
{
 
262
  if (cipherText.left(3) == "+p ") //don't encode if...?
 
263
    cipherText = cipherText.mid(3);
 
264
  else
 
265
  {
 
266
    if(m_cbc) //encode in ecb or cbc decide how to determine later
 
267
    {
 
268
      QByteArray temp = blowfishCBC(cipherText, true);
 
269
 
 
270
      if(temp == cipherText)
 
271
      {
 
272
        // kDebug("CBC Encoding Failed");
 
273
        return false;
 
274
      }
 
275
 
 
276
      cipherText = "+OK *" + temp;
 
277
    }
 
278
    else
 
279
    {
 
280
      QByteArray temp = blowfishECB(cipherText, true);
 
281
 
 
282
      if(temp == cipherText)
 
283
      {
 
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
  {
 
301
    // make sure cipherText is an interval of 8 bits. We do this before so that we
 
302
    // know there's at least 8 bytes to en/decryption this ensures QCA doesn't fail
 
303
    while((temp.length() % 8) != 0) 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
  {
 
310
    temp = QByteArray::fromBase64(temp);
 
311
    //supposedly nescessary if we get a truncated message also allows for decryption of 'crazy'
 
312
    //en/decoding clients that use STANDARDIZED PADDING TECHNIQUES
 
313
    while((temp.length() % 8) != 0) 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
  {
 
340
    while((temp.length() % 8) != 0) temp.append('\0');
 
341
  }
 
342
  else
 
343
  {
 
344
    temp = b64ToByte(temp);
 
345
    while((temp.length() % 8) != 0) temp.append('\0');
 
346
  }
 
347
 
 
348
  QCA::Direction dir = (direction) ? QCA::Encode : QCA::Decode;
 
349
  QCA::Cipher cipher(m_type, QCA::Cipher::ECB, QCA::Cipher::NoPadding, dir, m_key);
 
350
  QByteArray temp2 = cipher.update(QCA::MemoryRegion(temp)).toByteArray();
 
351
  temp2 += cipher.final().toByteArray();
 
352
 
 
353
  if(!cipher.ok())
 
354
    return cipherText;
 
355
 
 
356
  if(direction)
 
357
    temp2 = byteToB64(temp2);
 
358
 
 
359
  return temp2;
 
360
}
 
361
 
 
362
//Custom non RFC 2045 compliant Base64 enc/dec code for mircryption / FiSH compatibility
 
363
QByteArray Cipher::byteToB64(QByteArray text)
 
364
{
 
365
  int left = 0;
 
366
  int right = 0;
 
367
  int k = -1;
 
368
  int v;
 
369
  QString base64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
 
370
  QByteArray encoded;
 
371
  while (k < (text.length() - 1)) {
 
372
    k++;
 
373
    v=text.at(k); if (v<0) v+=256;
 
374
    left = v << 24;
 
375
    k++;
 
376
    v=text.at(k); if (v<0) v+=256;
 
377
    left += v << 16;
 
378
    k++;
 
379
    v=text.at(k); if (v<0) v+=256;
 
380
    left += v << 8;
 
381
    k++;
 
382
    v=text.at(k); if (v<0) v+=256;
 
383
    left += v;
 
384
 
 
385
    k++;
 
386
    v=text.at(k); if (v<0) v+=256;
 
387
    right = v << 24;
 
388
    k++;
 
389
    v=text.at(k); if (v<0) v+=256;
 
390
    right += v << 16;
 
391
    k++;
 
392
    v=text.at(k); if (v<0) v+=256;
 
393
    right += v << 8;
 
394
    k++;
 
395
    v=text.at(k); if (v<0) v+=256;
 
396
    right += v;
 
397
 
 
398
    for (int i = 0; i < 6; i++) {
 
399
      encoded.append(base64.at(right & 0x3F).toAscii());
 
400
      right = right >> 6;
 
401
    }
 
402
 
 
403
    //TODO make sure the .toascii doesn't break anything
 
404
    for (int i = 0; i < 6; i++) {
 
405
      encoded.append(base64.at(left & 0x3F).toAscii());
 
406
      left = left >> 6;
 
407
    }
 
408
  }
 
409
  return encoded;
 
410
}
 
411
 
 
412
QByteArray Cipher::b64ToByte(QByteArray text)
 
413
{
 
414
  QString base64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
 
415
  QByteArray decoded;
 
416
  int k = -1;
 
417
  while (k < (text.length() - 1)) {
 
418
    int right = 0;
 
419
    int left = 0;
 
420
    int v = 0;
 
421
    int w = 0;
 
422
    int z = 0;
 
423
 
 
424
    for (int i = 0; i < 6; i++) {
 
425
      k++;
 
426
      v = base64.indexOf(text.at(k));
 
427
      right |= v << (i * 6);
 
428
    }
 
429
 
 
430
    for (int i = 0; i < 6; i++) {
 
431
      k++;
 
432
      v = base64.indexOf(text.at(k));
 
433
      left |= v << (i * 6);
 
434
    }
 
435
 
 
436
    for (int i = 0; i < 4; i++) {
 
437
      w = ((left & (0xFF << ((3 - i) * 8))));
 
438
      z = w >> ((3 - i) * 8);
 
439
      if(z < 0) {z = z + 256;}
 
440
      decoded.append(z);
 
441
    }
 
442
 
 
443
    for (int i = 0; i < 4; i++) {
 
444
      w = ((right & (0xFF << ((3 - i) * 8))));
 
445
      z = w >> ((3 - i) * 8);
 
446
      if(z < 0) {z = z + 256;}
 
447
      decoded.append(z);
 
448
    }
 
449
  }
 
450
  return decoded;
 
451
}