testclientsideencryption.cpp 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. /*
  2. This software is in the public domain, furnished "as is", without technical
  3. support, and with no warranty, express or implied, as to its usefulness for
  4. any purpose.
  5. */
  6. #include <QtTest>
  7. #include <QTemporaryFile>
  8. #include <QRandomGenerator>
  9. #include <common/constants.h>
  10. #include "clientsideencryption.h"
  11. using namespace OCC;
  12. class TestClientSideEncryption : public QObject
  13. {
  14. Q_OBJECT
  15. QByteArray convertToOldStorageFormat(const QByteArray &data)
  16. {
  17. return data.split('|').join("fA==");
  18. }
  19. private slots:
  20. void shouldEncryptPrivateKeys()
  21. {
  22. // GIVEN
  23. const auto encryptionKey = QByteArrayLiteral("foo");
  24. const auto privateKey = QByteArrayLiteral("bar");
  25. const auto originalSalt = QByteArrayLiteral("baz");
  26. // WHEN
  27. const auto cipher = EncryptionHelper::encryptPrivateKey(encryptionKey, privateKey, originalSalt);
  28. // THEN
  29. const auto parts = cipher.split('|');
  30. QCOMPARE(parts.size(), 3);
  31. const auto encryptedKey = QByteArray::fromBase64(parts[0]);
  32. const auto iv = QByteArray::fromBase64(parts[1]);
  33. const auto salt = QByteArray::fromBase64(parts[2]);
  34. // We're not here to check the merits of the encryption but at least make sure it's been
  35. // somewhat ciphered
  36. QVERIFY(!encryptedKey.isEmpty());
  37. QVERIFY(encryptedKey != privateKey);
  38. QVERIFY(!iv.isEmpty());
  39. QCOMPARE(salt, originalSalt);
  40. }
  41. void shouldDecryptPrivateKeys()
  42. {
  43. // GIVEN
  44. const auto encryptionKey = QByteArrayLiteral("foo");
  45. const auto originalPrivateKey = QByteArrayLiteral("bar");
  46. const auto originalSalt = QByteArrayLiteral("baz");
  47. const auto cipher = EncryptionHelper::encryptPrivateKey(encryptionKey, originalPrivateKey, originalSalt);
  48. // WHEN
  49. const auto privateKey = EncryptionHelper::decryptPrivateKey(encryptionKey, cipher);
  50. const auto salt = EncryptionHelper::extractPrivateKeySalt(cipher);
  51. // THEN
  52. QCOMPARE(privateKey, originalPrivateKey);
  53. QCOMPARE(salt, originalSalt);
  54. }
  55. void shouldDecryptPrivateKeysInOldStorageFormat()
  56. {
  57. // GIVEN
  58. const auto encryptionKey = QByteArrayLiteral("foo");
  59. const auto originalPrivateKey = QByteArrayLiteral("bar");
  60. const auto originalSalt = QByteArrayLiteral("baz");
  61. const auto cipher = convertToOldStorageFormat(EncryptionHelper::encryptPrivateKey(encryptionKey, originalPrivateKey, originalSalt));
  62. // WHEN
  63. const auto privateKey = EncryptionHelper::decryptPrivateKey(encryptionKey, cipher);
  64. const auto salt = EncryptionHelper::extractPrivateKeySalt(cipher);
  65. // THEN
  66. QCOMPARE(privateKey, originalPrivateKey);
  67. QCOMPARE(salt, originalSalt);
  68. }
  69. void shouldSymmetricEncryptStrings()
  70. {
  71. // GIVEN
  72. const auto encryptionKey = QByteArrayLiteral("foo");
  73. const auto data = QByteArrayLiteral("bar");
  74. // WHEN
  75. const auto cipher = EncryptionHelper::encryptStringSymmetric(encryptionKey, data);
  76. // THEN
  77. const auto parts = cipher.split('|');
  78. QCOMPARE(parts.size(), 2);
  79. const auto encryptedData = QByteArray::fromBase64(parts[0]);
  80. const auto iv = QByteArray::fromBase64(parts[1]);
  81. // We're not here to check the merits of the encryption but at least make sure it's been
  82. // somewhat ciphered
  83. QVERIFY(!encryptedData.isEmpty());
  84. QVERIFY(encryptedData != data);
  85. QVERIFY(!iv.isEmpty());
  86. }
  87. void shouldSymmetricDecryptStrings()
  88. {
  89. // GIVEN
  90. const auto encryptionKey = QByteArrayLiteral("foo");
  91. const auto originalData = QByteArrayLiteral("bar");
  92. const auto cipher = EncryptionHelper::encryptStringSymmetric(encryptionKey, originalData);
  93. // WHEN
  94. const auto data = EncryptionHelper::decryptStringSymmetric(encryptionKey, cipher);
  95. // THEN
  96. QCOMPARE(data, originalData);
  97. }
  98. void shouldSymmetricDecryptStringsInOldStorageFormat()
  99. {
  100. // GIVEN
  101. const auto encryptionKey = QByteArrayLiteral("foo");
  102. const auto originalData = QByteArrayLiteral("bar");
  103. const auto cipher = convertToOldStorageFormat(EncryptionHelper::encryptStringSymmetric(encryptionKey, originalData));
  104. // WHEN
  105. const auto data = EncryptionHelper::decryptStringSymmetric(encryptionKey, cipher);
  106. // THEN
  107. QCOMPARE(data, originalData);
  108. }
  109. void testStreamingDecryptor_data()
  110. {
  111. QTest::addColumn<int>("totalBytes");
  112. QTest::addColumn<int>("bytesToRead");
  113. QTest::newRow("data1") << 64 << 2;
  114. QTest::newRow("data2") << 32 << 8;
  115. QTest::newRow("data3") << 76 << 64;
  116. QTest::newRow("data4") << 272 << 256;
  117. }
  118. void testStreamingDecryptor()
  119. {
  120. QFETCH(int, totalBytes);
  121. QTemporaryFile dummyInputFile;
  122. QVERIFY(dummyInputFile.open());
  123. const auto dummyFileRandomContents = EncryptionHelper::generateRandom(totalBytes);
  124. QCOMPARE(dummyInputFile.write(dummyFileRandomContents), dummyFileRandomContents.size());
  125. const auto generateHash = [](const QByteArray &data) {
  126. QCryptographicHash hash(QCryptographicHash::Sha1);
  127. hash.addData(data);
  128. return hash.result();
  129. };
  130. const QByteArray originalFileHash = generateHash(dummyFileRandomContents);
  131. QVERIFY(!originalFileHash.isEmpty());
  132. dummyInputFile.close();
  133. QVERIFY(!dummyInputFile.isOpen());
  134. const auto encryptionKey = EncryptionHelper::generateRandom(16);
  135. const auto initializationVector = EncryptionHelper::generateRandom(16);
  136. // test normal file encryption/decryption
  137. QTemporaryFile dummyEncryptionOutputFile;
  138. QByteArray tag;
  139. QVERIFY(EncryptionHelper::fileEncryption(encryptionKey, initializationVector, &dummyInputFile, &dummyEncryptionOutputFile, tag));
  140. dummyInputFile.close();
  141. QVERIFY(!dummyInputFile.isOpen());
  142. dummyEncryptionOutputFile.close();
  143. QVERIFY(!dummyEncryptionOutputFile.isOpen());
  144. QTemporaryFile dummyDecryptionOutputFile;
  145. QVERIFY(EncryptionHelper::fileDecryption(encryptionKey, initializationVector, &dummyEncryptionOutputFile, &dummyDecryptionOutputFile));
  146. QVERIFY(dummyDecryptionOutputFile.open());
  147. const auto dummyDecryptionOutputFileHash = generateHash(dummyDecryptionOutputFile.readAll());
  148. QCOMPARE(dummyDecryptionOutputFileHash, originalFileHash);
  149. // test streaming decryptor
  150. EncryptionHelper::StreamingDecryptor streamingDecryptor(encryptionKey, initializationVector, dummyEncryptionOutputFile.size());
  151. QVERIFY(streamingDecryptor.isInitialized());
  152. QBuffer chunkedOutputDecrypted;
  153. QVERIFY(chunkedOutputDecrypted.open(QBuffer::WriteOnly));
  154. QVERIFY(dummyEncryptionOutputFile.open());
  155. QByteArray pendingBytes;
  156. QFETCH(int, bytesToRead);
  157. while (dummyEncryptionOutputFile.pos() < dummyEncryptionOutputFile.size()) {
  158. const auto bytesRemaining = dummyEncryptionOutputFile.size() - dummyEncryptionOutputFile.pos();
  159. auto toRead = bytesRemaining > bytesToRead ? bytesToRead : bytesRemaining;
  160. if (dummyEncryptionOutputFile.pos() + toRead > dummyEncryptionOutputFile.size()) {
  161. toRead = dummyEncryptionOutputFile.size() - dummyEncryptionOutputFile.pos();
  162. }
  163. if (bytesRemaining - toRead != 0 && bytesRemaining - toRead < OCC::Constants::e2EeTagSize) {
  164. // decryption is going to fail if last chunk does not include or does not equal to OCC::Constants::e2EeTagSize bytes tag
  165. // since we are emulating random size of network packets, we may end up reading beyond OCC::Constants::e2EeTagSize bytes tag at the end
  166. // in that case, we don't want to try and decrypt less than OCC::Constants::e2EeTagSize ending bytes of tag, we will accumulate all the incoming data till the end
  167. // and then, we are going to decrypt the entire chunk containing OCC::Constants::e2EeTagSize bytes at the end
  168. pendingBytes += dummyEncryptionOutputFile.read(bytesRemaining);
  169. continue;
  170. }
  171. const auto decryptedChunk = streamingDecryptor.chunkDecryption(dummyEncryptionOutputFile.read(toRead).constData(), toRead);
  172. QVERIFY(decryptedChunk.size() == toRead || streamingDecryptor.isFinished() || !pendingBytes.isEmpty());
  173. chunkedOutputDecrypted.write(decryptedChunk);
  174. }
  175. if (!pendingBytes.isEmpty()) {
  176. const auto decryptedChunk = streamingDecryptor.chunkDecryption(pendingBytes.constData(), pendingBytes.size());
  177. QVERIFY(decryptedChunk.size() == pendingBytes.size() || streamingDecryptor.isFinished());
  178. chunkedOutputDecrypted.write(decryptedChunk);
  179. }
  180. chunkedOutputDecrypted.close();
  181. QVERIFY(chunkedOutputDecrypted.open(QBuffer::ReadOnly));
  182. QCOMPARE(generateHash(chunkedOutputDecrypted.readAll()), originalFileHash);
  183. chunkedOutputDecrypted.close();
  184. }
  185. };
  186. QTEST_APPLESS_MAIN(TestClientSideEncryption)
  187. #include "testclientsideencryption.moc"