| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- /*
- This software is in the public domain, furnished "as is", without technical
- support, and with no warranty, express or implied, as to its usefulness for
- any purpose.
- */
- #include <QtTest>
- #include <QTemporaryFile>
- #include <QRandomGenerator>
- #include <common/constants.h>
- #include "clientsideencryption.h"
- using namespace OCC;
- class TestClientSideEncryption : public QObject
- {
- Q_OBJECT
- QByteArray convertToOldStorageFormat(const QByteArray &data)
- {
- return data.split('|').join("fA==");
- }
- private slots:
- void shouldEncryptPrivateKeys()
- {
- // GIVEN
- const auto encryptionKey = QByteArrayLiteral("foo");
- const auto privateKey = QByteArrayLiteral("bar");
- const auto originalSalt = QByteArrayLiteral("baz");
- // WHEN
- const auto cipher = EncryptionHelper::encryptPrivateKey(encryptionKey, privateKey, originalSalt);
- // THEN
- const auto parts = cipher.split('|');
- QCOMPARE(parts.size(), 3);
- const auto encryptedKey = QByteArray::fromBase64(parts[0]);
- const auto iv = QByteArray::fromBase64(parts[1]);
- const auto salt = QByteArray::fromBase64(parts[2]);
- // We're not here to check the merits of the encryption but at least make sure it's been
- // somewhat ciphered
- QVERIFY(!encryptedKey.isEmpty());
- QVERIFY(encryptedKey != privateKey);
- QVERIFY(!iv.isEmpty());
- QCOMPARE(salt, originalSalt);
- }
- void shouldDecryptPrivateKeys()
- {
- // GIVEN
- const auto encryptionKey = QByteArrayLiteral("foo");
- const auto originalPrivateKey = QByteArrayLiteral("bar");
- const auto originalSalt = QByteArrayLiteral("baz");
- const auto cipher = EncryptionHelper::encryptPrivateKey(encryptionKey, originalPrivateKey, originalSalt);
- // WHEN
- const auto privateKey = EncryptionHelper::decryptPrivateKey(encryptionKey, cipher);
- const auto salt = EncryptionHelper::extractPrivateKeySalt(cipher);
- // THEN
- QCOMPARE(privateKey, originalPrivateKey);
- QCOMPARE(salt, originalSalt);
- }
- void shouldDecryptPrivateKeysInOldStorageFormat()
- {
- // GIVEN
- const auto encryptionKey = QByteArrayLiteral("foo");
- const auto originalPrivateKey = QByteArrayLiteral("bar");
- const auto originalSalt = QByteArrayLiteral("baz");
- const auto cipher = convertToOldStorageFormat(EncryptionHelper::encryptPrivateKey(encryptionKey, originalPrivateKey, originalSalt));
- // WHEN
- const auto privateKey = EncryptionHelper::decryptPrivateKey(encryptionKey, cipher);
- const auto salt = EncryptionHelper::extractPrivateKeySalt(cipher);
- // THEN
- QCOMPARE(privateKey, originalPrivateKey);
- QCOMPARE(salt, originalSalt);
- }
- void shouldSymmetricEncryptStrings()
- {
- // GIVEN
- const auto encryptionKey = QByteArrayLiteral("foo");
- const auto data = QByteArrayLiteral("bar");
- // WHEN
- const auto cipher = EncryptionHelper::encryptStringSymmetric(encryptionKey, data);
- // THEN
- const auto parts = cipher.split('|');
- QCOMPARE(parts.size(), 2);
- const auto encryptedData = QByteArray::fromBase64(parts[0]);
- const auto iv = QByteArray::fromBase64(parts[1]);
- // We're not here to check the merits of the encryption but at least make sure it's been
- // somewhat ciphered
- QVERIFY(!encryptedData.isEmpty());
- QVERIFY(encryptedData != data);
- QVERIFY(!iv.isEmpty());
- }
- void shouldSymmetricDecryptStrings()
- {
- // GIVEN
- const auto encryptionKey = QByteArrayLiteral("foo");
- const auto originalData = QByteArrayLiteral("bar");
- const auto cipher = EncryptionHelper::encryptStringSymmetric(encryptionKey, originalData);
- // WHEN
- const auto data = EncryptionHelper::decryptStringSymmetric(encryptionKey, cipher);
- // THEN
- QCOMPARE(data, originalData);
- }
- void shouldSymmetricDecryptStringsInOldStorageFormat()
- {
- // GIVEN
- const auto encryptionKey = QByteArrayLiteral("foo");
- const auto originalData = QByteArrayLiteral("bar");
- const auto cipher = convertToOldStorageFormat(EncryptionHelper::encryptStringSymmetric(encryptionKey, originalData));
- // WHEN
- const auto data = EncryptionHelper::decryptStringSymmetric(encryptionKey, cipher);
- // THEN
- QCOMPARE(data, originalData);
- }
- void testStreamingDecryptor_data()
- {
- QTest::addColumn<int>("totalBytes");
- QTest::addColumn<int>("bytesToRead");
- QTest::newRow("data1") << 64 << 2;
- QTest::newRow("data2") << 32 << 8;
- QTest::newRow("data3") << 76 << 64;
- QTest::newRow("data4") << 272 << 256;
- }
- void testStreamingDecryptor()
- {
- QFETCH(int, totalBytes);
- QTemporaryFile dummyInputFile;
- QVERIFY(dummyInputFile.open());
- const auto dummyFileRandomContents = EncryptionHelper::generateRandom(totalBytes);
- QCOMPARE(dummyInputFile.write(dummyFileRandomContents), dummyFileRandomContents.size());
- const auto generateHash = [](const QByteArray &data) {
- QCryptographicHash hash(QCryptographicHash::Sha1);
- hash.addData(data);
- return hash.result();
- };
- const QByteArray originalFileHash = generateHash(dummyFileRandomContents);
- QVERIFY(!originalFileHash.isEmpty());
- dummyInputFile.close();
- QVERIFY(!dummyInputFile.isOpen());
- const auto encryptionKey = EncryptionHelper::generateRandom(16);
- const auto initializationVector = EncryptionHelper::generateRandom(16);
- // test normal file encryption/decryption
- QTemporaryFile dummyEncryptionOutputFile;
- QByteArray tag;
- QVERIFY(EncryptionHelper::fileEncryption(encryptionKey, initializationVector, &dummyInputFile, &dummyEncryptionOutputFile, tag));
- dummyInputFile.close();
- QVERIFY(!dummyInputFile.isOpen());
- dummyEncryptionOutputFile.close();
- QVERIFY(!dummyEncryptionOutputFile.isOpen());
- QTemporaryFile dummyDecryptionOutputFile;
- QVERIFY(EncryptionHelper::fileDecryption(encryptionKey, initializationVector, &dummyEncryptionOutputFile, &dummyDecryptionOutputFile));
- QVERIFY(dummyDecryptionOutputFile.open());
- const auto dummyDecryptionOutputFileHash = generateHash(dummyDecryptionOutputFile.readAll());
- QCOMPARE(dummyDecryptionOutputFileHash, originalFileHash);
- // test streaming decryptor
- EncryptionHelper::StreamingDecryptor streamingDecryptor(encryptionKey, initializationVector, dummyEncryptionOutputFile.size());
- QVERIFY(streamingDecryptor.isInitialized());
- QBuffer chunkedOutputDecrypted;
- QVERIFY(chunkedOutputDecrypted.open(QBuffer::WriteOnly));
- QVERIFY(dummyEncryptionOutputFile.open());
- QByteArray pendingBytes;
- QFETCH(int, bytesToRead);
- while (dummyEncryptionOutputFile.pos() < dummyEncryptionOutputFile.size()) {
- const auto bytesRemaining = dummyEncryptionOutputFile.size() - dummyEncryptionOutputFile.pos();
- auto toRead = bytesRemaining > bytesToRead ? bytesToRead : bytesRemaining;
- if (dummyEncryptionOutputFile.pos() + toRead > dummyEncryptionOutputFile.size()) {
- toRead = dummyEncryptionOutputFile.size() - dummyEncryptionOutputFile.pos();
- }
- if (bytesRemaining - toRead != 0 && bytesRemaining - toRead < OCC::Constants::e2EeTagSize) {
- // decryption is going to fail if last chunk does not include or does not equal to OCC::Constants::e2EeTagSize bytes tag
- // since we are emulating random size of network packets, we may end up reading beyond OCC::Constants::e2EeTagSize bytes tag at the end
- // 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
- // and then, we are going to decrypt the entire chunk containing OCC::Constants::e2EeTagSize bytes at the end
- pendingBytes += dummyEncryptionOutputFile.read(bytesRemaining);
- continue;
- }
- const auto decryptedChunk = streamingDecryptor.chunkDecryption(dummyEncryptionOutputFile.read(toRead).constData(), toRead);
- QVERIFY(decryptedChunk.size() == toRead || streamingDecryptor.isFinished() || !pendingBytes.isEmpty());
- chunkedOutputDecrypted.write(decryptedChunk);
- }
- if (!pendingBytes.isEmpty()) {
- const auto decryptedChunk = streamingDecryptor.chunkDecryption(pendingBytes.constData(), pendingBytes.size());
- QVERIFY(decryptedChunk.size() == pendingBytes.size() || streamingDecryptor.isFinished());
- chunkedOutputDecrypted.write(decryptedChunk);
- }
- chunkedOutputDecrypted.close();
- QVERIFY(chunkedOutputDecrypted.open(QBuffer::ReadOnly));
- QCOMPARE(generateHash(chunkedOutputDecrypted.readAll()), originalFileHash);
- chunkedOutputDecrypted.close();
- }
- };
- QTEST_APPLESS_MAIN(TestClientSideEncryption)
- #include "testclientsideencryption.moc"
|